1007 lines
37 KiB
Python
1007 lines
37 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
|
||
|
"""Apply configuration of EOLE servers.
|
||
|
|
||
|
"""
|
||
|
|
||
|
import os
|
||
|
import argparse
|
||
|
import time
|
||
|
import shutil
|
||
|
|
||
|
from glob import glob
|
||
|
|
||
|
import spwd
|
||
|
import getpass
|
||
|
from itertools import count
|
||
|
|
||
|
from pyeole.log import getLogger
|
||
|
from pyeole.log import init_logging
|
||
|
from pyeole.log import set_formatter
|
||
|
from pyeole.log import set_filters
|
||
|
|
||
|
from pyeole import scriptargs
|
||
|
from pyeole.lock import acquire, release
|
||
|
from pyeole import process
|
||
|
from pyeole.schedule import display_schedules, apply_schedules
|
||
|
from pyeole import ihm
|
||
|
from pyeole.pkg import report, EolePkg, _configure_sources_mirror, _MIRROR_DIST
|
||
|
from pyeole.pkg import PackageNotFoundError, RepositoryError, AptProxyError, AptCacherError
|
||
|
from pyeole.service import manage_service, unmanaged_service, manage_services, \
|
||
|
ServiceError
|
||
|
from pyeole.encode import normalize
|
||
|
from pyeole.diagnose.diagnose import MAJ_SUCCES_LOCK
|
||
|
|
||
|
from .error import FileNotFound, LockError, UnlockError
|
||
|
from .error import UserExit, UserExitError
|
||
|
from .error import VirtError
|
||
|
from .client import CreoleClient, CreoleClientError, NotFoundError
|
||
|
import fonctionseole, template, cert
|
||
|
from .eosfunc import is_instanciate
|
||
|
from .config import configeol, INSTANCE_LOCKFILE, UPGRADE_LOCKFILE, \
|
||
|
container_instance_lockfile, gen_conteneurs_needed, VIRTROOT, charset
|
||
|
from .containers import is_lxc_enabled, is_lxc_running, is_lxc_started, \
|
||
|
generate_lxc_container, create_mount_point, lxc_need_restart
|
||
|
from .error import NetworkConfigError
|
||
|
|
||
|
from pyeole.i18n import i18n
|
||
|
_ = i18n('creole')
|
||
|
|
||
|
try:
|
||
|
from zephir.lib_zephir import lock, unlock
|
||
|
zephir_libs = True
|
||
|
except Exception:
|
||
|
zephir_libs = False
|
||
|
|
||
|
client = CreoleClient()
|
||
|
|
||
|
global PKGMGR
|
||
|
PKGMGR = None
|
||
|
|
||
|
error_msg_documentation = _(u"""For more informations, read section
|
||
|
'Mise en œuvre des modules EOLE' in module documentation or
|
||
|
common documentation.""")
|
||
|
def load_pkgmgr():
|
||
|
global PKGMGR
|
||
|
if PKGMGR is None:
|
||
|
cache()
|
||
|
PKGMGR = EolePkg('apt', container_mode=CACHE['is_lxc_enabled'])
|
||
|
PKGMGR.pkgmgr.groups = CACHE
|
||
|
PKGMGR.pkgmgr._load_apt_cache()
|
||
|
eoles = []
|
||
|
for eole in client.get_creole(u'serveur_maj'):
|
||
|
eoles.append('http://{0}/eole/'.format(eole))
|
||
|
ubuntus = []
|
||
|
for ubuntu in client.get_creole(u'ubuntu_update_mirrors'):
|
||
|
ubuntus.append('http://{0}/ubuntu/'.format(ubuntu))
|
||
|
envoles = []
|
||
|
try:
|
||
|
for envole in client.get_creole(u'envole_update_mirrors'):
|
||
|
envoles.append('http://{0}/envole/'.format(envole))
|
||
|
except NotFoundError:
|
||
|
pass
|
||
|
for cache_ in PKGMGR.pkgmgr.cache._list.list:
|
||
|
if cache_.uri in eoles:
|
||
|
PKGMGR.pkgmgr._test_mirror(cache_.uri, _MIRROR_DIST['EOLE'])
|
||
|
eoles = []
|
||
|
if cache_.uri in ubuntus:
|
||
|
PKGMGR.pkgmgr._test_mirror(cache_.uri, _MIRROR_DIST['Ubuntu'])
|
||
|
ubuntus = []
|
||
|
if cache_.uri in envoles:
|
||
|
PKGMGR.pkgmgr._test_mirror(cache_.uri, _MIRROR_DIST['Envole'])
|
||
|
envoles = []
|
||
|
fonctionseole.PkgManager = PKGMGR
|
||
|
|
||
|
_LOGFILENAME = '/var/log/reconfigure.log'
|
||
|
|
||
|
# Command line options
|
||
|
class Option:
|
||
|
"""Manage commande line options with defaults
|
||
|
|
||
|
"""
|
||
|
def __init__(self):
|
||
|
self.parser = argparse.ArgumentParser(
|
||
|
description=_(u"Applying EOLE configuration."),
|
||
|
parents=[scriptargs.container(),
|
||
|
scriptargs.logging(level='info')])
|
||
|
self.parser.add_argument('-i', '--interactive', action='store_true',
|
||
|
help=_(u"leave process in interactive mode"))
|
||
|
self.parser.add_argument('-f', '--force', action='store_true',
|
||
|
help=_(u"override Zéphir lock"))
|
||
|
self.parser.add_argument('-a', '--auto', action='store_true',
|
||
|
help=_(u"automatic reboot if necessary"))
|
||
|
self.__opts = self.parser.parse_args([])
|
||
|
|
||
|
def update_from_cmdline(self, force_args=None, force_options=None):
|
||
|
"""Parse command line
|
||
|
"""
|
||
|
self.__opts = self.parser.parse_args(force_args)
|
||
|
if self.__opts.verbose:
|
||
|
self.__opts.log_level = 'info'
|
||
|
if self.__opts.debug:
|
||
|
self.__opts.log_level = 'debug'
|
||
|
if force_options is not None:
|
||
|
for key, value in force_options.items():
|
||
|
setattr(self.__opts, key, value)
|
||
|
self.__dict__.update(self.__opts.__dict__)
|
||
|
|
||
|
def __getattr__(self, name):
|
||
|
if name in ['__opts', 'update_from_cmdline']:
|
||
|
return self.__dict__[name]
|
||
|
else:
|
||
|
return getattr(self.__opts, name)
|
||
|
|
||
|
options = Option()
|
||
|
|
||
|
# To use log from every functions
|
||
|
log = getLogger(__name__)
|
||
|
|
||
|
# Same name for instance and reconfigure
|
||
|
LOCK_NAME = u'reconfigure'
|
||
|
|
||
|
# Run scripts in directories
|
||
|
RUNPARTS_PATH = u'/usr/share/eole'
|
||
|
RUNPARTS_CMD = u'/bin/run-parts --exit-on-error -v {directory} --arg {compat} 2>&1'
|
||
|
|
||
|
# Compatibility
|
||
|
COMPAT_NAME = u'reconfigure'
|
||
|
|
||
|
#def parse_cmdline():
|
||
|
# """Parse command line
|
||
|
# """
|
||
|
# descr = u"Application de la configuration EOLE"
|
||
|
# parser = argparse.ArgumentParser(description=descr,
|
||
|
# parents=[scriptargs.container(),
|
||
|
# scriptargs.logging(level='info')])
|
||
|
# parser.add_argument('-i', '--interactive', action='store_true',
|
||
|
# help=u"lancer le processus en mode interactif")
|
||
|
# parser.add_argument('-f', '--force', action='store_true',
|
||
|
# help=u"force l'action même s'il existe des verrous")
|
||
|
# parser.add_argument('-a', '--auto', action='store_true',
|
||
|
# help=u"redémarrage automatique si nécessaire")
|
||
|
#
|
||
|
# opts = parser.parse_args()
|
||
|
# if opts.verbose:
|
||
|
# opts.log_level = 'info'
|
||
|
# if opts.debug:
|
||
|
# opts.log_level = 'debug'
|
||
|
# return opts
|
||
|
|
||
|
def copyDirectoryContent(src, dst):
|
||
|
for fic in os.listdir(src):
|
||
|
# Skip links or we ovewrite existing certificates
|
||
|
if os.path.islink(os.path.join(src, fic)):
|
||
|
continue
|
||
|
try:
|
||
|
shutil.copy2(os.path.join(src, fic), dst)
|
||
|
except shutil.Error, err:
|
||
|
# ignore if files already exists
|
||
|
pass
|
||
|
|
||
|
def user_exit(*args, **kwargs):
|
||
|
"""
|
||
|
sortie utilisateur "propre"
|
||
|
"""
|
||
|
log.warn(_(u'! Abandoning configuration !'))
|
||
|
log.warn(_(u'System may be in an incoherent state.\n\n'))
|
||
|
raise UserExitError()
|
||
|
|
||
|
def unlock_actions(need_lock=True):
|
||
|
if zephir_libs:
|
||
|
#FIXME: lock de Zephir !
|
||
|
unlock('actions')
|
||
|
try:
|
||
|
release(LOCK_NAME, level='system')
|
||
|
except Exception, err:
|
||
|
# FIXME: move lock exception to pyeole.lock #7400
|
||
|
if need_lock:
|
||
|
raise UnlockError(str(err))
|
||
|
|
||
|
def lock_actions():
|
||
|
try:
|
||
|
acquire(LOCK_NAME, level="system")
|
||
|
except Exception, err:
|
||
|
# FIXME: move lock exception to pyeole.lock #7400
|
||
|
raise LockError(str(err))
|
||
|
if zephir_libs:
|
||
|
#FIXME: lock de Zephir !
|
||
|
lock('actions')
|
||
|
|
||
|
def reset_compat_name():
|
||
|
"""
|
||
|
Réinitialise le nom de la procédure en cours
|
||
|
en fonction de l'environnement
|
||
|
"""
|
||
|
global COMPAT_NAME
|
||
|
if options.interactive:
|
||
|
COMPAT_NAME = u'instance'
|
||
|
else:
|
||
|
COMPAT_NAME = u'reconfigure'
|
||
|
|
||
|
def run_parts(directory):
|
||
|
"""Run scripts in a directory
|
||
|
|
||
|
@param directory: name of a directory
|
||
|
@type directory: C{str}
|
||
|
"""
|
||
|
dirpath = os.path.join(RUNPARTS_PATH, directory)
|
||
|
if os.path.isdir(dirpath):
|
||
|
ihm.print_title(_(u'Running scripts {0}').format(directory))
|
||
|
code = os.system(RUNPARTS_CMD.format(directory=dirpath, compat=COMPAT_NAME))
|
||
|
if code != 0:
|
||
|
raise Exception(_(u'Error {0}').format(directory))
|
||
|
|
||
|
def restart_creoled():
|
||
|
"""
|
||
|
Restart creoled service and verify if the client is OK
|
||
|
"""
|
||
|
unmanaged_service(u'restart', u'creoled', u'service', display='console')
|
||
|
try:
|
||
|
client.get_creole(u'eole_version')
|
||
|
except CreoleClientError:
|
||
|
msg = _(u"Please check creoled's log (/var/log/rsyslog/local/creoled/creoled.info.log)\nand restart service with command 'service creoled start'")
|
||
|
raise CreoleClientError(msg)
|
||
|
|
||
|
def prepare(need_lock=True):
|
||
|
"""Sanity checks.
|
||
|
|
||
|
"""
|
||
|
global RUNPARTS_CMD
|
||
|
# Clean exit
|
||
|
if need_lock:
|
||
|
ihm.catch_signal(user_exit)
|
||
|
lock_actions()
|
||
|
|
||
|
if options.container != None:
|
||
|
RUNPARTS_CMD += u" --regex '^[09][09]-{0}$'".format(options.container)
|
||
|
|
||
|
ihm.print_title(_(u"Preparation for {0}").format(COMPAT_NAME))
|
||
|
|
||
|
if not os.path.isfile(configeol):
|
||
|
print _(u"Server is not configured.")
|
||
|
print
|
||
|
print error_msg_documentation
|
||
|
print
|
||
|
raise FileNotFound(_(u'Missing file {0}.').format(configeol))
|
||
|
|
||
|
display_info = False
|
||
|
|
||
|
if not options.interactive and (is_instanciate() == 'non' or os.path.isfile(UPGRADE_LOCKFILE)):
|
||
|
ihm.print_red(_(u"Server must be instantiated before any reconfiguration can occur."))
|
||
|
display_info = True
|
||
|
|
||
|
if options.interactive and is_instanciate() == 'oui' and \
|
||
|
not os.path.isfile(UPGRADE_LOCKFILE) and \
|
||
|
not os.path.isfile(container_instance_lockfile):
|
||
|
ihm.print_red(_(u"Server already instantiated."))
|
||
|
print
|
||
|
print _(u"To modify configuration parameter (e.g. IP address), use:")
|
||
|
print _(u"'gen_config'")
|
||
|
print _(u"then 'reconfigure' to apply changes.")
|
||
|
display_info = True
|
||
|
|
||
|
if os.path.isfile(container_instance_lockfile) and not options.interactive:
|
||
|
raise Exception(_('you have run gen_conteneurs, please use instance instead of reconfigure'))
|
||
|
|
||
|
if os.path.isfile(gen_conteneurs_needed):
|
||
|
raise Exception(_('You have to run gen_conteneurs before instance'))
|
||
|
|
||
|
if display_info:
|
||
|
print
|
||
|
print error_msg_documentation
|
||
|
print
|
||
|
if not options.interactive:
|
||
|
raise Exception(_(u"First instantiate server."))
|
||
|
else:
|
||
|
if ihm.prompt_boolean(_(u"Proceeding with instantiation ?"),
|
||
|
interactive=options.interactive,
|
||
|
default=False) is False:
|
||
|
raise UserExit()
|
||
|
else:
|
||
|
fonctionseole.zephir("MSG", "Instance forcée par l'utilisateur",
|
||
|
COMPAT_NAME.upper())
|
||
|
|
||
|
# redémarrage du service creoled
|
||
|
restart_creoled()
|
||
|
|
||
|
if fonctionseole.init_proc(COMPAT_NAME.upper()) == False and not options.force:
|
||
|
log.warn(_(u"This process is blocked, contact Zéphir administrator."))
|
||
|
if ihm.prompt_boolean(_(u"Force execution?"),
|
||
|
interactive=options.interactive,
|
||
|
default=False) is False:
|
||
|
if not options.interactive:
|
||
|
log.warn(_(u"Use -f option if you want to force execution"))
|
||
|
raise UserExitError()
|
||
|
else:
|
||
|
fonctionseole.zephir("MSG",
|
||
|
"Instance forcée par l'utilisateur",
|
||
|
COMPAT_NAME.upper())
|
||
|
|
||
|
|
||
|
def valid_mandatory(need_lock):
|
||
|
try:
|
||
|
client.valid_mandatory()
|
||
|
except Exception, err:
|
||
|
log.warn(_('Configuration validation problem, please check server configuration.'))
|
||
|
print
|
||
|
print error_msg_documentation
|
||
|
print
|
||
|
unlock_actions(need_lock)
|
||
|
raise ValueError(str(err))
|
||
|
|
||
|
def _start_containers():
|
||
|
""" Try to start containers and make sure they are started
|
||
|
"""
|
||
|
cache()
|
||
|
for group_name in CACHE['groups_container']:
|
||
|
group = CACHE['group_infos'][group_name]
|
||
|
create_mount_point(group)
|
||
|
|
||
|
if os.access('/usr/share/eole/preservice/00-lxc-net', os.X_OK):
|
||
|
log.debug("Override lxc-net systemd script")
|
||
|
process.system_code(['/usr/share/eole/preservice/00-lxc-net'])
|
||
|
|
||
|
unmanaged_service(u'start', u'lxc-net', u'systemd', display='console', ctx=CACHE['group_infos']['root'])
|
||
|
try:
|
||
|
unmanaged_service(u'status', u'lxc', u'systemd')
|
||
|
except ServiceError:
|
||
|
unmanaged_service(u'start', u'lxc', u'systemd', display='console', ctx=CACHE['group_infos']['root'])
|
||
|
#if lxc not started, do not wait for it
|
||
|
#(we already waiting for it in systemd service)
|
||
|
#if started, waiting for ssh access
|
||
|
|
||
|
max_try = 10
|
||
|
for count in range(max_try):
|
||
|
s_code, s_out, s_err = process.system_out(['lxc-ls', '--stopped'])
|
||
|
stopped = s_out.split()
|
||
|
f_code, f_out, f_err = process.system_out(['lxc-ls', '--frozen'])
|
||
|
frozen = f_out.split()
|
||
|
|
||
|
if stopped or frozen:
|
||
|
not_running = stopped + frozen
|
||
|
else:
|
||
|
# Everything is started by LXC
|
||
|
# Are they reachable by SSH?
|
||
|
not_running = []
|
||
|
for group_name in CACHE['groups_container']:
|
||
|
group_infos = CACHE['group_infos'][group_name]
|
||
|
if not is_lxc_running(group_infos):
|
||
|
not_running.append(group_name)
|
||
|
|
||
|
log.debug('Waiting 1 second for SSH access')
|
||
|
time.sleep(1)
|
||
|
|
||
|
if not not_running:
|
||
|
break
|
||
|
|
||
|
if stopped:
|
||
|
for cont in stopped:
|
||
|
log.debug('Manual start of stopped container “{0}”'.format(cont))
|
||
|
process.system_out(['lxc-start', '--name', cont, '--daemon',
|
||
|
'-o', '/var/log/lxc-{0}.log'.format(cont)])
|
||
|
|
||
|
if frozen:
|
||
|
for cont in frozen:
|
||
|
log.debug('Manual unfreeze of frozen container “{0}”'.format(cont))
|
||
|
process.system_out(['lxc-unfreeze', '--name', cont,
|
||
|
'-o', '/var/log/lxc-{0}.log'.format(cont)])
|
||
|
|
||
|
if not_running:
|
||
|
waiting_for = ', '.join(not_running)
|
||
|
msg = _(u'Unable to start LXC container : {0}',
|
||
|
u'Unable to start LXC containers : {0}', len(not_running))
|
||
|
raise VirtError(msg.format(waiting_for))
|
||
|
|
||
|
|
||
|
def containers(minimal=False, log_=None):
|
||
|
"""Generate containers
|
||
|
"""
|
||
|
if log_ is None:
|
||
|
log_ = log
|
||
|
VAR_LXC='/var/lib/lxc'
|
||
|
OPT_LXC='/opt/lxc'
|
||
|
|
||
|
cache()
|
||
|
if not CACHE['is_lxc_enabled']:
|
||
|
log.debug(_(u'Container mode is disabled.'))
|
||
|
return True
|
||
|
if not options.interactive:
|
||
|
for group in CACHE['groups_container']:
|
||
|
if not os.path.isdir(os.path.join(VIRTROOT, group)):
|
||
|
raise Exception(_(u'container {0} does not already exist, please use gen_conteneurs to create this container').format(group))
|
||
|
else:
|
||
|
# make /var/lib/lxc -> /opt/lxc
|
||
|
if os.path.isdir(VAR_LXC) and not os.path.exists(OPT_LXC):
|
||
|
ihm.print_title(_(u"Setting up {0}").format(OPT_LXC))
|
||
|
unmanaged_service(u'stop', u'lxc', u'systemd', display='console')
|
||
|
unmanaged_service(u'stop', u'lxc-net', u'systemd', display='console')
|
||
|
shutil.move(VAR_LXC, OPT_LXC)
|
||
|
os.symlink(OPT_LXC, VAR_LXC)
|
||
|
#first instance should be in minimal mode
|
||
|
minimal = True
|
||
|
|
||
|
ihm.print_title(_(u'Generating containers'))
|
||
|
|
||
|
engine = template.CreoleTemplateEngine()
|
||
|
rootctx = CACHE['group_infos']['root']
|
||
|
if minimal:
|
||
|
# inject var _minimal_mode in creole's vars that can be used in template
|
||
|
engine.creole_variables_dict['_minimal_mode'] = True
|
||
|
engine.instance_file(u'/etc/ssh/ssh_config', ctx=rootctx)
|
||
|
engine.instance_file(u'/etc/lxc/default.conf', ctx=rootctx)
|
||
|
engine.instance_file(u'/etc/dnsmasq.d/lxc', ctx=rootctx)
|
||
|
engine.instance_file(u'/etc/default/lxc-net', ctx=rootctx)
|
||
|
engine.instance_file(u'/etc/apt/apt.conf.d/02eoleproxy', ctx=rootctx)
|
||
|
if CACHE['module_instancie'] == 'oui':
|
||
|
engine.instance_file(u'/etc/resolv.conf', ctx=rootctx)
|
||
|
|
||
|
load_pkgmgr()
|
||
|
PKGMGR.pkgmgr._prepare_cache()
|
||
|
for group in CACHE['groups_container']:
|
||
|
generate_lxc_container(group)
|
||
|
groupctx = CACHE['group_infos'][group]
|
||
|
if minimal:
|
||
|
engine.instance_file(u'../fstab', container=group, ctx=groupctx)
|
||
|
engine.instance_file(u'../config', container=group, ctx=groupctx)
|
||
|
engine.instance_file(u'../devices.hook', container=group, ctx=groupctx)
|
||
|
engine.instance_file(u'/etc/network/interfaces', container=group, ctx=groupctx)
|
||
|
engine.instance_file(u'/etc/apt/apt.conf.d/02eoleproxy', container=group, ctx=groupctx)
|
||
|
engine.instance_file(u'/etc/ssh/sshd_config', container=group, ctx=groupctx)
|
||
|
if CACHE['module_instancie'] == 'oui':
|
||
|
container_path = os.path.join(groupctx['path'], 'etc/resolv.conf')
|
||
|
if os.path.islink(container_path):
|
||
|
os.remove(container_path)
|
||
|
engine.instance_file(u'/etc/resolv.conf', container=group, ctx=groupctx)
|
||
|
PKGMGR.pkgmgr._umount_cdrom()
|
||
|
|
||
|
ihm.print_title(_(u'Starting containers'))
|
||
|
_start_containers()
|
||
|
|
||
|
def remove_packages():
|
||
|
""" Remove packages listed in /usr/share/eole/remove.d/ files
|
||
|
param: repo: EoleApt Object
|
||
|
"""
|
||
|
torm_conf = glob(u'/usr/share/eole/remove.d/*.conf')
|
||
|
pkg_list = []
|
||
|
for config in torm_conf:
|
||
|
try:
|
||
|
f_h = open(config, 'r')
|
||
|
for line in f_h.readlines():
|
||
|
pkg_list.append(line.strip('\n'))
|
||
|
f_h.close()
|
||
|
except IOError, err:
|
||
|
log.error(_(u'Can not read file {0}: {1}').format(config, err))
|
||
|
|
||
|
try:
|
||
|
load_pkgmgr()
|
||
|
except (RepositoryError, AptProxyError, AptCacherError), err:
|
||
|
pass
|
||
|
|
||
|
kernels = fonctionseole.get_kernel_to_remove()
|
||
|
|
||
|
if kernels:
|
||
|
ihm.print_line(_(u"Removing old linux kernels and associate headers."))
|
||
|
pkg_list.extend(kernels)
|
||
|
|
||
|
if pkg_list != []:
|
||
|
try:
|
||
|
PKGMGR.remove(packages=pkg_list)
|
||
|
except (PackageNotFoundError, SystemError), err:
|
||
|
msg = _(u'Unable to remove some packages: {0}')
|
||
|
log.warn(msg.format(err))
|
||
|
log.warn(_(u"These packages will be removed next 'reconfigure'"))
|
||
|
|
||
|
|
||
|
CACHE = {}
|
||
|
def cache():
|
||
|
global CACHE
|
||
|
if not 'groups' in CACHE:
|
||
|
CACHE['groups'] = client.get_groups()
|
||
|
CACHE['groups_container'] = []
|
||
|
for group in CACHE['groups']:
|
||
|
if group not in ['root', 'all']:
|
||
|
CACHE['groups_container'].append(group)
|
||
|
CACHE['group_infos'] = {}
|
||
|
for group_name in CACHE['groups']:
|
||
|
group_infos = client.get_group_infos(group_name)
|
||
|
CACHE['group_infos'][group_name] = group_infos
|
||
|
CACHE['is_lxc_enabled'] = is_lxc_enabled()
|
||
|
CACHE['module_instancie'] = client.get_creole('module_instancie')
|
||
|
|
||
|
|
||
|
|
||
|
def install_packages(silent=False):
|
||
|
"""Install package for each container group
|
||
|
"""
|
||
|
load_pkgmgr()
|
||
|
|
||
|
cache()
|
||
|
header = _(u'Checking Packages for container')
|
||
|
for group_name, group_infos in CACHE['group_infos'].items():
|
||
|
package_names = [pkg[u'name'] for pkg in group_infos[u'packages']]
|
||
|
if package_names != []:
|
||
|
msg = header + ' {0}: {1}'.format(group_name, ' '.join(package_names))
|
||
|
ihm.print_line(msg)
|
||
|
PKGMGR.install(packages=package_names,
|
||
|
silent=silent,
|
||
|
container=group_infos[u'name'])
|
||
|
|
||
|
|
||
|
def packages():
|
||
|
"""Manage packages
|
||
|
"""
|
||
|
ihm.print_title(_(u'Managing packages'))
|
||
|
log.info(_(u' Removing packages'))
|
||
|
ihm.print_line(_(u'Removing packages'))
|
||
|
remove_packages()
|
||
|
log.info(_(u' Installing packages'))
|
||
|
ihm.print_line(_(u'Installing packages'))
|
||
|
install_packages()
|
||
|
|
||
|
|
||
|
def templates():
|
||
|
"""Run pretemplate scripts and manage templates
|
||
|
"""
|
||
|
ihm.print_title(_(u'Generating configuration files'))
|
||
|
log.info(_(u'Generating configuration files'))
|
||
|
cache()
|
||
|
try:
|
||
|
tmpl = template.CreoleTemplateEngine()
|
||
|
tmpl.instance_files(container=options.container, containers_ctx=CACHE['group_infos'].values())
|
||
|
except Exception, err:
|
||
|
if options.debug:
|
||
|
log.debug(err, exc_info=True)
|
||
|
else:
|
||
|
log.error(err)
|
||
|
raise err
|
||
|
|
||
|
|
||
|
def services(action, display_title=True, try_restart_lxc=True):
|
||
|
"""Manage services
|
||
|
"""
|
||
|
cache()
|
||
|
exclude = None
|
||
|
if action == u'stop':
|
||
|
if display_title:
|
||
|
ihm.print_title(_(u"Stopping services"))
|
||
|
exclude = (('root', 'networking'),)
|
||
|
elif action == u'start':
|
||
|
if display_title:
|
||
|
ihm.print_title(_(u"Starting services"))
|
||
|
# ne pas demarrer le service certbot, c'est un service oneshot
|
||
|
# et pyeole.service n'a pas l'air d'aimer ... #22092
|
||
|
exclude = (('root', 'networking'), ('root', 'certbot'))
|
||
|
ctx = CACHE['group_infos']['root']
|
||
|
manage_services(action, u'networking', display='console', containers_ctx=[ctx])
|
||
|
if try_restart_lxc and CACHE['is_lxc_enabled']:
|
||
|
if lxc_need_restart():
|
||
|
unmanaged_service(u'stop', u'lxc', u'systemd', display='console', ctx=ctx)
|
||
|
unmanaged_service(u'stop', u'lxc-net', u'systemd', display='console', ctx=ctx)
|
||
|
_start_containers()
|
||
|
elif action == u'configure':
|
||
|
if display_title:
|
||
|
ihm.print_title(_(u"Configuring services"))
|
||
|
else:
|
||
|
raise ValueError(_(u"Unknown service action: {0}").format(action))
|
||
|
if options.container is not None:
|
||
|
containers_ctx = [CACHE['group_infos'][options.containers]]
|
||
|
else:
|
||
|
containers_ctx = CACHE['group_infos'].values()
|
||
|
manage_services(action, container=options.container, display='console', exclude=exclude, containers_ctx=containers_ctx)
|
||
|
|
||
|
|
||
|
def _gen_user_list():
|
||
|
"""Generate list of users for password modification
|
||
|
|
||
|
Start with basic one and ask for supplementary users.
|
||
|
"""
|
||
|
yield 'root'
|
||
|
|
||
|
node = client.get_creole(u'activer_onenode', 'non')
|
||
|
master = client.get_creole(u'activer_onesinglenode', 'non')
|
||
|
if node == 'oui' and master == 'non':
|
||
|
yield 'oneadmin'
|
||
|
|
||
|
for number in count(1):
|
||
|
if number == 1:
|
||
|
yield 'eole'
|
||
|
else:
|
||
|
yield 'eole{0}'.format(number)
|
||
|
|
||
|
|
||
|
|
||
|
def users():
|
||
|
"""Manage users
|
||
|
"""
|
||
|
from passlib.context import CryptContext
|
||
|
ihm.print_title(_(u'Managing system user accounts'))
|
||
|
schemes = [u'sha512_crypt', u'sha256_crypt', u'sha1_crypt', u'md5_crypt']
|
||
|
cryptctx = CryptContext(schemes=schemes)
|
||
|
default_pass = {u'root': u'$eole&123456$',
|
||
|
u'eole': u'$fpmf&123456$',
|
||
|
u'oneadmin': u'$eole&123456$'}
|
||
|
|
||
|
if not options.interactive:
|
||
|
log.debug(_(u'No system user account management in non-interactive mode.'))
|
||
|
return
|
||
|
|
||
|
for user in _gen_user_list():
|
||
|
try:
|
||
|
user_infos = spwd.getspnam(user)
|
||
|
except KeyError:
|
||
|
if user == u'root':
|
||
|
msg = _(u"'root' user unknown. This is abnormal.")
|
||
|
raise Exception(msg)
|
||
|
|
||
|
# no new administrator with NFS (#16321)
|
||
|
if user != 'eole' and client.get_creole(u'adresse_serveur_nfs', None) is not None:
|
||
|
log.warn(_(u'No new EOLE account with /home on NFS'))
|
||
|
break
|
||
|
|
||
|
prompt = _('Create new administrator user account {0}?')
|
||
|
if user != 'eole' and ihm.prompt_boolean(prompt.format(user)) is False:
|
||
|
break
|
||
|
|
||
|
msg = _(u"Creating unexistent user {0}")
|
||
|
log.info(msg.format(user))
|
||
|
|
||
|
cmd = ['adduser', '--quiet', '--shell', '/usr/bin/manage-eole',
|
||
|
'--gecos', '{0} user'.format(user.upper()),
|
||
|
'--disabled-password', user]
|
||
|
code = process.system_code(cmd)
|
||
|
if code != 0:
|
||
|
msg = _(u"Unable to create user {0}")
|
||
|
raise Exception(msg.format(user))
|
||
|
|
||
|
cmd = ['usermod', '--append', '--groups', 'adm,mail', user]
|
||
|
code, out, err = process.system_out(cmd)
|
||
|
if code != 0:
|
||
|
msg = _(u"Unable to add '{0}' to group 'adm'.")
|
||
|
raise Exception(msg.format(user))
|
||
|
|
||
|
# Update informations
|
||
|
user_infos = spwd.getspnam(user)
|
||
|
|
||
|
if user not in default_pass and user_infos.sp_pwd not in ['!', '*']:
|
||
|
msg = _(u"No modification of password of administrator user account {0}.")
|
||
|
log.warn(msg.format(user))
|
||
|
continue
|
||
|
|
||
|
# Change password:
|
||
|
# - on first instance
|
||
|
# - if user is not an EOLE default user
|
||
|
# - if user password match default ones
|
||
|
if (not os.path.isfile(INSTANCE_LOCKFILE)
|
||
|
or (user not in default_pass or user_infos.sp_pwd in ['!', '*']
|
||
|
or cryptctx.verify(default_pass[user], user_infos.sp_pwd))):
|
||
|
|
||
|
msg = _(u"# Modificating password for user account {0} #")
|
||
|
msg = msg.format(user)
|
||
|
log.warn(u'#' * len(msg))
|
||
|
log.warn(msg)
|
||
|
log.warn(u'#' * len(msg))
|
||
|
max_try = 5
|
||
|
prompt = u'{0}{1}: '
|
||
|
first_prompt = _(u"New password")
|
||
|
second_prompt = _(u"Confirming new password")
|
||
|
loop_counter = u''
|
||
|
for attempt in range(1, max_try+2):
|
||
|
if attempt == max_try+1:
|
||
|
msg = _(u"Password input errors for {0}. Abandon.")
|
||
|
raise Exception(msg.format(user))
|
||
|
|
||
|
loop_counter = loop_counter.format(attempt, max_try)
|
||
|
passwd = getpass.getpass(prompt.format(first_prompt,
|
||
|
loop_counter))
|
||
|
confirm_pass = getpass.getpass(prompt.format(second_prompt,
|
||
|
loop_counter))
|
||
|
if passwd == confirm_pass:
|
||
|
if user in default_pass and default_pass[user] == passwd:
|
||
|
log.error(_(u"Can not use default password."))
|
||
|
else:
|
||
|
# Now we have the password
|
||
|
stdin = '{0}:{1}'.format(user, passwd)
|
||
|
code, stdout, stderr = process.system_out(['chpasswd'],
|
||
|
stdin=stdin)
|
||
|
if code == 0:
|
||
|
msg = _(u'User {0} password updated.')
|
||
|
log.info(msg.format(user))
|
||
|
# Success
|
||
|
break
|
||
|
msg = _(u"Error changing password for {0}.")
|
||
|
try_again_pos = stdout.find('Try again.')
|
||
|
chpassmsg = stdout[0:try_again_pos]
|
||
|
log.error(msg.format(user))
|
||
|
print chpassmsg
|
||
|
else:
|
||
|
log.error(_(u"Passwords mismatch."))
|
||
|
|
||
|
# Display counter
|
||
|
loop_counter = u' ({0}/{1})'
|
||
|
|
||
|
|
||
|
def certificates():
|
||
|
"""Manage certificates
|
||
|
|
||
|
"""
|
||
|
ihm.print_title(_(u'Managing certificates'))
|
||
|
try:
|
||
|
# regénération des hashes des certificats SSL après avec créé les nouveaux certificats
|
||
|
# porté de 2.3 #8488
|
||
|
cert.rehash_if_needed()
|
||
|
cert.gen_certs()
|
||
|
except Exception, err:
|
||
|
if options.debug:
|
||
|
log.debug(err, exc_info=True)
|
||
|
else:
|
||
|
log.error(err)
|
||
|
raise Exception(_(u"Error while generating certificates: {0}").format(err))
|
||
|
cache()
|
||
|
if CACHE['is_lxc_enabled']:
|
||
|
src = os.path.join(cert.ssl_dir, "certs")
|
||
|
for group_name in CACHE['groups_container']:
|
||
|
group = CACHE['group_infos'][group_name]
|
||
|
ihm.print_line(_("Copying certificates in {0}").format(group['name']))
|
||
|
dst = os.path.join('/', group['path'].lstrip('/').encode(charset), src.lstrip('/'))
|
||
|
copyDirectoryContent(src, dst)
|
||
|
process.system_out(['/usr/bin/c_rehash'], container=group_name)
|
||
|
|
||
|
|
||
|
def param_kernel():
|
||
|
"""Manage kernel parameters
|
||
|
"""
|
||
|
ihm.print_title(_(u'Applying kernel parameters'))
|
||
|
os.system('/sbin/sysctl -p >/dev/null')
|
||
|
|
||
|
def kill_dhclient():
|
||
|
"""Kill dhclient for static IP configuration.
|
||
|
|
||
|
"""
|
||
|
if client.get_creole(u'eth0_method') == u'statique':
|
||
|
os.system('killall dhclient dhclient3 2>/dev/null')
|
||
|
|
||
|
def finalize(need_lock=True):
|
||
|
"""Clean up
|
||
|
"""
|
||
|
ihm.print_title(_(u'Finalizing configuration'))
|
||
|
# enregistrement
|
||
|
try:
|
||
|
process.system_out("/usr/share/creole/diag.py")
|
||
|
except Exception:
|
||
|
pass
|
||
|
fonctionseole.zephir("FIN", "Configuration terminée", COMPAT_NAME.upper())
|
||
|
if not os.path.isfile(INSTANCE_LOCKFILE):
|
||
|
# l'instance est allée à son terme (#7051)
|
||
|
file(INSTANCE_LOCKFILE, 'w').close()
|
||
|
|
||
|
if os.path.isfile(UPGRADE_LOCKFILE):
|
||
|
os.unlink(UPGRADE_LOCKFILE)
|
||
|
|
||
|
if os.path.isfile(container_instance_lockfile):
|
||
|
os.unlink(container_instance_lockfile)
|
||
|
|
||
|
# sauvegarde des 2 dernières versions de la configuration (#8455)
|
||
|
old = '{0}.bak'.format(configeol)
|
||
|
old1 = '{0}.bak.1'.format(configeol)
|
||
|
if not os.path.isfile(old):
|
||
|
log.debug(_(u'Backup {0} in {1}'.format(configeol, old)))
|
||
|
shutil.copy(configeol, old)
|
||
|
elif process.system_out(['diff', '-q', configeol, old])[0] == 0:
|
||
|
log.debug(_(u"{0} was not modified".format(configeol)))
|
||
|
else:
|
||
|
log.debug(_(u'Backup {0} in {1}'.format(old, old1)))
|
||
|
shutil.copy(old, old1)
|
||
|
log.debug(_(u'Backup {0} in {1}'.format(configeol, old)))
|
||
|
shutil.copy(configeol, old)
|
||
|
if need_lock:
|
||
|
unlock_actions()
|
||
|
|
||
|
def update_server():
|
||
|
"""Manage server update
|
||
|
"""
|
||
|
if os.path.isfile(MAJ_SUCCES_LOCK):
|
||
|
os.remove(MAJ_SUCCES_LOCK)
|
||
|
if options.interactive:
|
||
|
log.info(_(u'Managing update'))
|
||
|
|
||
|
ihm.print_title(_(u'Updating server'))
|
||
|
if ihm.prompt_boolean(_(u"""An update is recommended.
|
||
|
Do you want to proceed with network update now ?"""),
|
||
|
default=True, level='warn',
|
||
|
default_uninteractive=False) is True:
|
||
|
report(2)
|
||
|
try:
|
||
|
load_pkgmgr()
|
||
|
_configure_sources_mirror(PKGMGR.pkgmgr)
|
||
|
PKGMGR.update(silent=True)
|
||
|
upgrades = PKGMGR.get_upgradable_list(silent=True)
|
||
|
require_dist_upgrade = False
|
||
|
for container, upgrades in upgrades.items():
|
||
|
if upgrades:
|
||
|
require_dist_upgrade = True
|
||
|
break
|
||
|
if require_dist_upgrade:
|
||
|
# At least one container require upgrade
|
||
|
PKGMGR.dist_upgrade()
|
||
|
# Update lock => OK, will be deleted at next reconfigure
|
||
|
report(0)
|
||
|
# recall reconfigure
|
||
|
main(force_options={'interactive': False})
|
||
|
# back to instance
|
||
|
options.interactive = True
|
||
|
reset_compat_name()
|
||
|
else:
|
||
|
log.warn(_(u"No updates available."))
|
||
|
report(3)
|
||
|
except Exception, err:
|
||
|
report(1, normalize(err))
|
||
|
raise err
|
||
|
|
||
|
|
||
|
def schedule():
|
||
|
"""Manage task scheduling
|
||
|
"""
|
||
|
ihm.print_title(_(u'Task scheduling'))
|
||
|
apply_schedules()
|
||
|
display_schedules()
|
||
|
# 1er lancement de instance
|
||
|
#if not os.path.isfile(schedule.SCHEDULE_FILE):
|
||
|
# schedule.add_post_schedule('majauto', 'weekly')
|
||
|
#schedule.prog_schedule()
|
||
|
|
||
|
def is_valid_ip_eth0():
|
||
|
"""Check if adresse_ip_eth0 is 169.254.0.1
|
||
|
"""
|
||
|
ip_eth0 = client.get_creole(u'adresse_ip_eth0')
|
||
|
if ip_eth0 == "169.254.0.1":
|
||
|
return False
|
||
|
else:
|
||
|
return True
|
||
|
|
||
|
|
||
|
def reboot_server():
|
||
|
"""Reboot the server if required
|
||
|
"""
|
||
|
if fonctionseole.controle_kernel():
|
||
|
if options.interactive:
|
||
|
print
|
||
|
if ihm.prompt_boolean(_(u"""Reboot is necessary.
|
||
|
Do you want to reboot now?"""),
|
||
|
default=True, level='warn') is True:
|
||
|
fonctionseole.zephir("MSG",
|
||
|
"Demande de redémarrage acceptée par l'utilisateur",
|
||
|
COMPAT_NAME.upper())
|
||
|
process.system_code(['reboot'])
|
||
|
else:
|
||
|
fonctionseole.zephir("MSG",
|
||
|
"Demande de redémarrage refusée par l'utilisateur",
|
||
|
COMPAT_NAME.upper())
|
||
|
else:
|
||
|
print
|
||
|
ihm.print_orange(_(u'Reboot necessary'))
|
||
|
time.sleep(1)
|
||
|
print
|
||
|
if options.auto:
|
||
|
fonctionseole.zephir("MSG", "Redémarrage automatique",
|
||
|
COMPAT_NAME.upper())
|
||
|
process.system_code(['reboot'])
|
||
|
else:
|
||
|
fonctionseole.zephir("MSG", "Redémarrage du serveur à planifier",
|
||
|
COMPAT_NAME.upper())
|
||
|
|
||
|
|
||
|
def main(force_options=None, force_args=None, need_lock=True):
|
||
|
"""Entry point
|
||
|
"""
|
||
|
global log
|
||
|
options.update_from_cmdline(force_args=force_args,
|
||
|
force_options=force_options)
|
||
|
|
||
|
try:
|
||
|
# module level logger
|
||
|
log = init_logging(name=u'reconfigure', level=options.log_level,
|
||
|
console=['stderr', 'stddebug'],
|
||
|
filename=_LOGFILENAME)
|
||
|
|
||
|
# Remove module name prefix from Warn/error messages emitted
|
||
|
# from here
|
||
|
set_formatter(log, 'stderr', 'brief')
|
||
|
|
||
|
# Define handlers for additional loggers
|
||
|
# Thoses logger are not for use
|
||
|
# Log pyeole.service
|
||
|
pyeole_service_log = init_logging(name=u'pyeole.service',
|
||
|
level=options.log_level,
|
||
|
filename=_LOGFILENAME,
|
||
|
console=['stderr'])
|
||
|
# Log pyeole.pkg
|
||
|
pyeole_pkg_log = init_logging(name=u'pyeole.pkg',
|
||
|
level=options.log_level,
|
||
|
filename=_LOGFILENAME)
|
||
|
passlib_log = init_logging(name=u'passlib.registry',
|
||
|
level='error',
|
||
|
filename=_LOGFILENAME)
|
||
|
|
||
|
# Disable warnings from pyeole.service
|
||
|
set_filters(pyeole_service_log, 'stderr',
|
||
|
['error', 'critical'])
|
||
|
|
||
|
if options.verbose or options.debug:
|
||
|
# Enable creole logs
|
||
|
creole_log = init_logging(name=u'creole', level=options.log_level,
|
||
|
filename=_LOGFILENAME)
|
||
|
# Define a root logger when verbose or debug is activated
|
||
|
root_log = init_logging(level=options.log_level)
|
||
|
else:
|
||
|
# Enable creole logs
|
||
|
creole_log = init_logging(name=u'creole', level=options.log_level,
|
||
|
filename=_LOGFILENAME,
|
||
|
console=['stderr'])
|
||
|
|
||
|
creolemajauto_log = init_logging(name=u'creole.majauto', level=options.log_level,
|
||
|
filename=_LOGFILENAME, console=['stderr', 'stdout'])
|
||
|
|
||
|
ihm.print_title(_(u'Beginning of configuration'))
|
||
|
# instance or reconfigure ?
|
||
|
reset_compat_name()
|
||
|
fonctionseole.zephir("INIT", "Début de configuration",
|
||
|
COMPAT_NAME.upper())
|
||
|
prepare(need_lock)
|
||
|
valid_mandatory(need_lock)
|
||
|
cache()
|
||
|
containers()
|
||
|
packages()
|
||
|
run_parts(u'preservice')
|
||
|
services(action=u'stop')
|
||
|
run_parts(u'pretemplate')
|
||
|
templates()
|
||
|
if not is_valid_ip_eth0():
|
||
|
log.info(_(u"eth0 network interface does not have a valid IP address."))
|
||
|
log.info(_(u"Restarting networking service"))
|
||
|
manage_service(u'restart', u'networking', display='console')
|
||
|
templates()
|
||
|
if not is_valid_ip_eth0():
|
||
|
log.info(_(u"eth0 network interface does not have a valid IP address."))
|
||
|
msg = _(u"Unable to obtain IP address.")
|
||
|
raise NetworkConfigError(msg)
|
||
|
|
||
|
services(action=u'configure')
|
||
|
# posttemplate/00-annuaire needs the certificates
|
||
|
certificates()
|
||
|
run_parts(u'posttemplate')
|
||
|
#close all connexion before param kernel #17408
|
||
|
client.close()
|
||
|
param_kernel()
|
||
|
kill_dhclient()
|
||
|
services(action=u'start')
|
||
|
users()
|
||
|
run_parts(u'postservice')
|
||
|
schedule()
|
||
|
finalize(need_lock)
|
||
|
ihm.print_title(_(u'Reconfiguration OK'))
|
||
|
update_server()
|
||
|
# IMPORTANT : Ne rien faire après ces lignes
|
||
|
# car le serveur est susceptible d'être redémarré
|
||
|
reboot_server()
|
||
|
|
||
|
except (UserExit, UserExitError), err:
|
||
|
unlock_actions(need_lock)
|
||
|
fonctionseole.zephir("FIN", "Abandon par l'utilisateur",
|
||
|
COMPAT_NAME.upper())
|
||
|
raise err
|
||
|
|
||
|
except Exception, err:
|
||
|
if options.debug:
|
||
|
log.debug(err, exc_info=True)
|
||
|
else:
|
||
|
log.error(err)
|
||
|
fonctionseole.zephir('ERR', str(err),
|
||
|
COMPAT_NAME.upper(),
|
||
|
console=False)
|
||
|
if need_lock:
|
||
|
release(LOCK_NAME, valid=False, level='system')
|
||
|
raise err
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
main()
|