Merge branch 'stretch-unstable' into authenticate-as-root

This commit is contained in:
Alexandre Aubin 2019-05-16 17:08:48 +02:00 committed by GitHub
commit 331bdb53aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 313 additions and 207 deletions

View file

@ -1449,11 +1449,11 @@ tools:
action_help: YunoHost update action_help: YunoHost update
api: PUT /update api: PUT /update
arguments: arguments:
--ignore-apps: --apps:
help: Ignore apps cache update and changelog help: Fetch the application list to check which apps can be upgraded
action: store_true action: store_true
--ignore-packages: --system:
help: Ignore APT cache update and changelog help: Fetch available system packages upgrades (equivalent to apt update)
action: store_true action: store_true
### tools_upgrade() ### tools_upgrade()
@ -1461,11 +1461,11 @@ tools:
action_help: YunoHost upgrade action_help: YunoHost upgrade
api: PUT /upgrade api: PUT /upgrade
arguments: arguments:
--ignore-apps: --apps:
help: Ignore apps upgrade help: List of apps to upgrade (all by default)
action: store_true nargs: "*"
--ignore-packages: --system:
help: Ignore APT packages upgrade help: Upgrade only the system packages
action: store_true action: store_true
### tools_diagnosis() ### tools_diagnosis()

View file

@ -2,6 +2,8 @@
set -e set -e
. /usr/share/yunohost/helpers
do_pre_regen() { do_pre_regen() {
pending_dir=$1 pending_dir=$1
@ -20,9 +22,12 @@ do_pre_regen() {
main_domain=$(cat /etc/yunohost/current_host) main_domain=$(cat /etc/yunohost/current_host)
domain_list=$(sudo yunohost domain list --output-as plain --quiet | tr '\n' ' ') domain_list=$(sudo yunohost domain list --output-as plain --quiet | tr '\n' ' ')
cat main.cf \ # Support different strategy for security configurations
| sed "s/{{ main_domain }}/${main_domain}/g" \ export compatibility="$(yunohost settings get 'security.postfix.compatibility')"
> "${postfix_dir}/main.cf"
export main_domain
export domain_list
ynh_render_template "main.cf" "${postfix_dir}/main.cf"
cat postsrsd \ cat postsrsd \
| sed "s/{{ main_domain }}/${main_domain}/g" \ | sed "s/{{ main_domain }}/${main_domain}/g" \

View file

@ -33,7 +33,11 @@ smtpd_tls_key_file = /etc/yunohost/certs/{{ main_domain }}/key.pem
smtpd_tls_exclude_ciphers = aNULL, MD5, DES, ADH, RC4, 3DES smtpd_tls_exclude_ciphers = aNULL, MD5, DES, ADH, RC4, 3DES
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtpd_tls_loglevel=1 smtpd_tls_loglevel=1
{% if compatibility == "intermediate" %}
smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3 smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
{% else %}
smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3,!TLSv1,!TLSv1.1
{% endif %}
smtpd_tls_mandatory_ciphers=high smtpd_tls_mandatory_ciphers=high
smtpd_tls_eecdh_grade = ultra smtpd_tls_eecdh_grade = ultra

View file

@ -4,8 +4,9 @@
"admin_password": "Administration password", "admin_password": "Administration password",
"admin_password_change_failed": "Unable to change password", "admin_password_change_failed": "Unable to change password",
"admin_password_changed": "The administration password has been changed", "admin_password_changed": "The administration password has been changed",
"app_action_cannot_be_ran_because_required_services_down": "This app requires some services which are currently down. Before continuing, you should try to restart the following services (and possibly investigate why they are down) : {services}",
"admin_password_too_long": "Please choose a password shorter than 127 characters", "admin_password_too_long": "Please choose a password shorter than 127 characters",
"already_up_to_date": "Nothing to do! Everything is already up to date!",
"app_action_cannot_be_ran_because_required_services_down": "This app requires some services which are currently down. Before continuing, you should try to restart the following services (and possibly investigate why they are down) : {services}",
"app_already_installed": "{app:s} is already installed", "app_already_installed": "{app:s} is already installed",
"app_already_installed_cant_change_url": "This app is already installed. The url cannot be changed just by this function. Look into `app changeurl` if it's available.", "app_already_installed_cant_change_url": "This app is already installed. The url cannot be changed just by this function. Look into `app changeurl` if it's available.",
"app_already_up_to_date": "{app:s} is already up to date", "app_already_up_to_date": "{app:s} is already up to date",
@ -216,6 +217,7 @@
"global_settings_setting_security_password_admin_strength": "Admin password strength", "global_settings_setting_security_password_admin_strength": "Admin password strength",
"global_settings_setting_security_password_user_strength": "User password strength", "global_settings_setting_security_password_user_strength": "User password strength",
"global_settings_setting_security_ssh_compatibility": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)", "global_settings_setting_security_ssh_compatibility": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)",
"global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)",
"global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/settings-unknown.json", "global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/settings-unknown.json",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", "global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration",
"global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.", "global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.",
@ -270,7 +272,7 @@
"log_tools_migrations_migrate_forward": "Migrate forward", "log_tools_migrations_migrate_forward": "Migrate forward",
"log_tools_migrations_migrate_backward": "Migrate backward", "log_tools_migrations_migrate_backward": "Migrate backward",
"log_tools_postinstall": "Postinstall your YunoHost server", "log_tools_postinstall": "Postinstall your YunoHost server",
"log_tools_upgrade": "Upgrade debian packages", "log_tools_upgrade": "Upgrade system packages",
"log_tools_shutdown": "Shutdown your server", "log_tools_shutdown": "Shutdown your server",
"log_tools_reboot": "Reboot your server", "log_tools_reboot": "Reboot your server",
"ldap_init_failed_to_create_admin": "LDAP initialization failed to create admin user", "ldap_init_failed_to_create_admin": "LDAP initialization failed to create admin user",
@ -367,7 +369,6 @@
"package_not_installed": "Package '{pkgname}' is not installed", "package_not_installed": "Package '{pkgname}' is not installed",
"package_unexpected_error": "An unexpected error occurred processing the package '{pkgname}'", "package_unexpected_error": "An unexpected error occurred processing the package '{pkgname}'",
"package_unknown": "Unknown package '{pkgname}'", "package_unknown": "Unknown package '{pkgname}'",
"packages_no_upgrade": "There is no package to upgrade",
"packages_upgrade_critical_later": "Critical packages ({packages:s}) will be upgraded later", "packages_upgrade_critical_later": "Critical packages ({packages:s}) will be upgraded later",
"packages_upgrade_failed": "Unable to upgrade all of the packages", "packages_upgrade_failed": "Unable to upgrade all of the packages",
"password_listed": "This password is among the most used password in the world. Please choose something a bit more unique.", "password_listed": "This password is among the most used password in the world. Please choose something a bit more unique.",
@ -482,6 +483,15 @@
"system_upgraded": "The system has been upgraded", "system_upgraded": "The system has been upgraded",
"system_username_exists": "Username already exists in the system users", "system_username_exists": "Username already exists in the system users",
"this_action_broke_dpkg": "This action broke dpkg/apt (the system package managers)... You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.", "this_action_broke_dpkg": "This action broke dpkg/apt (the system package managers)... You can try to solve this issue by connecting through SSH and running `sudo dpkg --configure -a`.",
"tools_upgrade_at_least_one": "Please specify --apps OR --system",
"tools_upgrade_cant_both": "Cannot upgrade both system and apps at the same time",
"tools_upgrade_cant_hold_critical_packages": "Unable to hold critical packages ...",
"tools_upgrade_cant_unhold_critical_packages": "Unable to unhold critical packages ...",
"tools_upgrade_regular_packages": "Now upgrading 'regular' (non-yunohost-related) packages ...",
"tools_upgrade_regular_packages_failed": "Unable to upgrade packages: {packages_list}",
"tools_upgrade_special_packages": "Now upgrading 'special' (yunohost-related) packages ...",
"tools_upgrade_special_packages_explanation": "This action will end but the actual special upgrade will continue in background. Please don't start any other action on your server in the next ~10 minutes (depending on your hardware speed). Once it's done, you may have to re-log on the webadmin. The upgrade log will be available in Tools > Log (in the webadmin) or through 'yunohost log list' (in command line).",
"tools_upgrade_special_packages_completed": "YunoHost package upgrade completed !\nPress [Enter] to get the command line back",
"unbackup_app": "App '{app:s}' will not be saved", "unbackup_app": "App '{app:s}' will not be saved",
"unexpected_error": "An unexpected error occured: {error}", "unexpected_error": "An unexpected error occured: {error}",
"unit_unknown": "Unknown unit '{unit:s}'", "unit_unknown": "Unknown unit '{unit:s}'",
@ -490,6 +500,7 @@
"update_apt_cache_failed": "Unable to update the cache of APT (Debian's package manager). Here is a dump of the sources.list lines which might help to identify problematic lines : \n{sourceslist}", "update_apt_cache_failed": "Unable to update the cache of APT (Debian's package manager). Here is a dump of the sources.list lines which might help to identify problematic lines : \n{sourceslist}",
"update_apt_cache_warning": "Some errors happened while updating the cache of APT (Debian's package manager). Here is a dump of the sources.list lines which might help to identify problematic lines : \n{sourceslist}", "update_apt_cache_warning": "Some errors happened while updating the cache of APT (Debian's package manager). Here is a dump of the sources.list lines which might help to identify problematic lines : \n{sourceslist}",
"updating_apt_cache": "Fetching available upgrades for system packages…", "updating_apt_cache": "Fetching available upgrades for system packages…",
"updating_app_lists": "Fetching available upgrades for applications…",
"upgrade_complete": "Upgrade complete", "upgrade_complete": "Upgrade complete",
"upgrading_packages": "Upgrading packages…", "upgrading_packages": "Upgrading packages…",
"upnp_dev_not_found": "No UPnP device found", "upnp_dev_not_found": "No UPnP device found",

View file

@ -697,10 +697,6 @@ def app_upgrade(app=[], url=None, file=None):
logger.success(m18n.n('upgrade_complete')) logger.success(m18n.n('upgrade_complete'))
# Return API logs if it is an API call
if is_api:
return {"log": service_log('yunohost-api', number="100").values()[0]}
@is_unit_operation() @is_unit_operation()
def app_install(operation_logger, app, label=None, args=None, no_remove_on_failure=False, force=False): def app_install(operation_logger, app, label=None, args=None, no_remove_on_failure=False, force=False):

View file

@ -346,8 +346,7 @@ class OperationLogger(object):
""" """
# TODO add a way to not save password on app installation # TODO add a way to not save password on app installation
filename = os.path.join(self.path, self.name + LOG_FILE_EXT) self.file_handler = FileHandler(self.log_path)
self.file_handler = FileHandler(filename)
self.file_handler.formatter = Formatter('%(asctime)s: %(levelname)s - %(message)s') self.file_handler.formatter = Formatter('%(asctime)s: %(levelname)s - %(message)s')
# Listen to the root logger # Listen to the root logger
@ -359,8 +358,7 @@ class OperationLogger(object):
Write or rewrite the metadata file with all metadata known Write or rewrite the metadata file with all metadata known
""" """
filename = os.path.join(self.path, self.name + METADATA_FILE_EXT) with open(self.md_path, 'w') as outfile:
with open(filename, 'w') as outfile:
yaml.safe_dump(self.metadata, outfile, default_flow_style=False) yaml.safe_dump(self.metadata, outfile, default_flow_style=False)
@property @property

View file

@ -44,6 +44,8 @@ DEFAULTS = OrderedDict([
"choices": ["intermediate", "modern"]}), "choices": ["intermediate", "modern"]}),
("security.nginx.compatibility", {"type": "enum", "default": "intermediate", ("security.nginx.compatibility", {"type": "enum", "default": "intermediate",
"choices": ["intermediate", "modern"]}), "choices": ["intermediate", "modern"]}),
("security.postfix.compatibility", {"type": "enum", "default": "intermediate",
"choices": ["intermediate", "modern"]}),
]) ])
@ -292,3 +294,8 @@ def reconfigure_nginx(setting_name, old_value, new_value):
def reconfigure_ssh(setting_name, old_value, new_value): def reconfigure_ssh(setting_name, old_value, new_value):
if old_value != new_value: if old_value != new_value:
service_regen_conf(names=['ssh']) service_regen_conf(names=['ssh'])
@post_change_hook("security.postfix.compatibility")
def reconfigure_ssh(setting_name, old_value, new_value):
if old_value != new_value:
service_regen_conf(names=['postfix'])

View file

@ -30,15 +30,11 @@ import json
import subprocess import subprocess
import pwd import pwd
import socket import socket
from glob import glob
from xmlrpclib import Fault from xmlrpclib import Fault
from importlib import import_module from importlib import import_module
from collections import OrderedDict from collections import OrderedDict
import apt from moulinette import msignals, m18n
import apt.progress
from moulinette import msettings, msignals, m18n
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from moulinette.utils.process import check_output, call_async_output from moulinette.utils.process import check_output, call_async_output
from moulinette.utils.filesystem import read_json, write_to_json from moulinette.utils.filesystem import read_json, write_to_json
@ -46,10 +42,10 @@ from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, a
from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain
from yunohost.dyndns import _dyndns_available, _dyndns_provides from yunohost.dyndns import _dyndns_available, _dyndns_provides
from yunohost.firewall import firewall_upnp from yunohost.firewall import firewall_upnp
from yunohost.service import service_status, service_log, service_start, service_enable from yunohost.service import service_status, service_start, service_enable
from yunohost.regenconf import regen_conf from yunohost.regenconf import regen_conf
from yunohost.monitor import monitor_disk, monitor_system from yunohost.monitor import monitor_disk, monitor_system
from yunohost.utils.packages import ynh_packages_version from yunohost.utils.packages import ynh_packages_version, _dump_sources_list, _list_upgradable_apt_packages
from yunohost.utils.network import get_public_ip from yunohost.utils.network import get_public_ip
from yunohost.utils.error import YunohostError from yunohost.utils.error import YunohostError
from yunohost.log import is_unit_operation, OperationLogger from yunohost.log import is_unit_operation, OperationLogger
@ -463,28 +459,31 @@ def tools_regen_conf(names=[], with_diff=False, force=False, dry_run=False,
return regen_conf(names, with_diff, force, dry_run, list_pending) return regen_conf(names, with_diff, force, dry_run, list_pending)
def tools_update(ignore_apps=False, ignore_packages=False): def tools_update(apps=False, system=False):
""" """
Update apps & package cache, then display changelog Update apps & system package cache
Keyword arguments: Keyword arguments:
ignore_apps -- Ignore app list update and changelog system -- Fetch available system packages upgrades (equivalent to apt update)
ignore_packages -- Ignore apt cache update and changelog apps -- Fetch the application list to check which apps can be upgraded
""" """
# "packages" will list upgradable packages
packages = [] # If neither --apps nor --system specified, do both
if not ignore_packages: if not apps and not system:
apps = True
system = True
upgradable_system_packages = []
if system:
# Update APT cache # Update APT cache
# LC_ALL=C is here to make sure the results are in english # LC_ALL=C is here to make sure the results are in english
command = "LC_ALL=C apt update" command = "LC_ALL=C apt update"
# TODO : add @is_unit_operation to tools_update so that the
# debug output can be fetched when there's an issue...
# Filter boring message about "apt not having a stable CLI interface" # Filter boring message about "apt not having a stable CLI interface"
# Also keep track of wether or not we encountered a warning... # Also keep track of wether or not we encountered a warning...
warnings = [] warnings = []
def is_legit_warning(m): def is_legit_warning(m):
legit_warning = m.rstrip() and "apt does not have a stable CLI interface" not in m.rstrip() legit_warning = m.rstrip() and "apt does not have a stable CLI interface" not in m.rstrip()
if legit_warning: if legit_warning:
@ -507,158 +506,201 @@ def tools_update(ignore_apps=False, ignore_packages=False):
elif warnings: elif warnings:
logger.error(m18n.n('update_apt_cache_warning', sourceslist='\n'.join(_dump_sources_list()))) logger.error(m18n.n('update_apt_cache_warning', sourceslist='\n'.join(_dump_sources_list())))
packages = list(_list_upgradable_apt_packages()) upgradable_system_packages = list(_list_upgradable_apt_packages())
logger.debug(m18n.n('done')) logger.debug(m18n.n('done'))
# "apps" will list upgradable packages upgradable_apps = []
apps = [] if apps:
if not ignore_apps: logger.info(m18n.n('updating_app_lists'))
try: try:
app_fetchlist() app_fetchlist()
except YunohostError: except YunohostError:
# FIXME : silent exception !? # FIXME : silent exception !?
pass pass
upgradable_apps = list(_list_upgradable_apps())
if len(upgradable_apps) == 0 and len(upgradable_system_packages) == 0:
logger.info(m18n.n('already_up_to_date'))
return {'system': upgradable_system_packages, 'apps': upgradable_apps}
def _list_upgradable_apps():
app_list_installed = os.listdir(APPS_SETTING_PATH) app_list_installed = os.listdir(APPS_SETTING_PATH)
for app_id in app_list_installed: for app_id in app_list_installed:
app_dict = app_info(app_id, raw=True) app_dict = app_info(app_id, raw=True)
if app_dict["upgradable"] == "yes": if app_dict["upgradable"] == "yes":
apps.append({ yield {
'id': app_id, 'id': app_id,
'label': app_dict['settings']['label'] 'label': app_dict['settings']['label']
})
if len(apps) == 0 and len(packages) == 0:
logger.info(m18n.n('packages_no_upgrade'))
return {'packages': packages, 'apps': apps}
# TODO : move this to utils/packages.py ?
def _list_upgradable_apt_packages():
# List upgradable packages
# LC_ALL=C is here to make sure the results are in english
upgradable_raw = check_output("LC_ALL=C apt list --upgradable")
# Dirty parsing of the output
upgradable_raw = [l.strip() for l in upgradable_raw.split("\n") if l.strip()]
for line in upgradable_raw:
# Remove stupid warning and verbose messages >.>
if "apt does not have a stable CLI interface" in line or "Listing..." in line:
continue
# line should look like :
# yunohost/stable 3.5.0.2+201903211853 all [upgradable from: 3.4.2.4+201903080053]
line = line.split()
if len(line) != 6:
logger.warning("Failed to parse this line : %s" % ' '.join(line))
continue
yield {
"name": line[0].split("/")[0],
"new_version": line[1],
"current_version": line[5].strip("]"),
} }
def _dump_sources_list():
filenames = glob("/etc/apt/sources.list") + glob("/etc/apt/sources.list.d/*")
for filename in filenames:
with open(filename, "r") as f:
for line in f.readlines():
if line.startswith("#") or not line.strip():
continue
yield filename.replace("/etc/apt/", "") + ":" + line.strip()
@is_unit_operation() @is_unit_operation()
def tools_upgrade(operation_logger, ignore_apps=False, ignore_packages=False): def tools_upgrade(operation_logger, apps=None, system=False):
""" """
Update apps & package cache, then display changelog Update apps & package cache, then display changelog
Keyword arguments: Keyword arguments:
ignore_apps -- Ignore apps upgrade apps -- List of apps to upgrade (or [] to update all apps)
ignore_packages -- Ignore APT packages upgrade system -- True to upgrade system
""" """
from yunohost.utils import packages from yunohost.utils import packages
if packages.dpkg_is_broken(): if packages.dpkg_is_broken():
raise YunohostError("dpkg_is_broken") raise YunohostError("dpkg_is_broken")
failure = False if system is not False and apps is not None:
raise YunohostError("tools_upgrade_cant_both")
# Retrieve interface if system is False and apps is None:
is_api = True if msettings.get('interface') == 'api' else False raise YunohostError("tools_upgrade_at_least_one")
if not ignore_packages: #
# Apps
# This is basically just an alias to yunohost app upgrade ...
#
apt.apt_pkg.init() if apps is not None:
apt.apt_pkg.config.set("DPkg::Options::", "--force-confdef")
apt.apt_pkg.config.set("DPkg::Options::", "--force-confold")
cache = apt.Cache() # Make sure there's actually something to upgrade
cache.open(None)
cache.upgrade(True)
# If API call upgradable_apps = [app["id"] for app in _list_upgradable_apps()]
if is_api:
critical_packages = ("moulinette", "yunohost",
"yunohost-admin", "ssowat", "python")
critical_upgrades = set()
for pkg in cache.get_changes(): if not upgradable_apps:
if pkg.name in critical_packages: logger.info(m18n.n("app_no_upgrade"))
critical_upgrades.add(pkg.name) return
# Temporarily keep package ... elif len(apps) and all(app not in upgradable_apps for app in apps):
pkg.mark_keep() logger.info(m18n.n("apps_already_up_to_date"))
return
# ... and set a hourly cron up to upgrade critical packages # Actually start the upgrades
if critical_upgrades:
logger.info(m18n.n('packages_upgrade_critical_later',
packages=', '.join(critical_upgrades)))
with open('/etc/cron.d/yunohost-upgrade', 'w+') as f:
f.write('00 * * * * root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin apt-get install %s -y && rm -f /etc/cron.d/yunohost-upgrade\n' % ' '.join(critical_upgrades))
if cache.get_changes():
logger.info(m18n.n('upgrading_packages'))
operation_logger.start()
try: try:
os.environ["DEBIAN_FRONTEND"] = "noninteractive" app_upgrade(app=apps)
# Apply APT changes
# TODO: Logs output for the API
cache.commit(apt.progress.text.AcquireProgress(),
apt.progress.base.InstallProgress())
except Exception as e: except Exception as e:
failure = True
logger.warning('unable to upgrade packages: %s' % str(e))
logger.error(m18n.n('packages_upgrade_failed'))
operation_logger.error(m18n.n('packages_upgrade_failed'))
else:
logger.info(m18n.n('done'))
operation_logger.success()
finally:
del os.environ["DEBIAN_FRONTEND"]
else:
logger.info(m18n.n('packages_no_upgrade'))
if not ignore_apps:
try:
app_upgrade()
except Exception as e:
failure = True
logger.warning('unable to upgrade apps: %s' % str(e)) logger.warning('unable to upgrade apps: %s' % str(e))
logger.error(m18n.n('app_upgrade_some_app_failed')) logger.error(m18n.n('app_upgrade_some_app_failed'))
if not failure: return
logger.success(m18n.n('system_upgraded'))
# Return API logs if it is an API call #
if is_api: # System
return {"log": service_log('yunohost-api', number="100").values()[0]} #
if system is True:
# Check that there's indeed some packages to upgrade
upgradables = list(_list_upgradable_apt_packages())
if not upgradables:
logger.info(m18n.n('already_up_to_date'))
logger.info(m18n.n('upgrading_packages'))
operation_logger.start()
# Critical packages are packages that we can't just upgrade
# randomly from yunohost itself... upgrading them is likely to
critical_packages = ("moulinette", "yunohost", "yunohost-admin", "ssowat", "python")
critical_packages_upgradable = [p for p in upgradables if p["name"] in critical_packages]
noncritical_packages_upgradable = [p for p in upgradables if p["name"] not in critical_packages]
# Prepare dist-upgrade command
dist_upgrade = "DEBIAN_FRONTEND=noninteractive"
dist_upgrade += " APT_LISTCHANGES_FRONTEND=none"
dist_upgrade += " apt-get"
dist_upgrade += " --fix-broken --show-upgraded --assume-yes"
for conf_flag in ["old", "miss", "def"]:
dist_upgrade += ' -o Dpkg::Options::="--force-conf{}"'.format(conf_flag)
dist_upgrade += " dist-upgrade"
#
# "Regular" packages upgrade
#
if noncritical_packages_upgradable:
logger.info(m18n.n("tools_upgrade_regular_packages"))
# Mark all critical packages as held
for package in critical_packages:
check_output("apt-mark hold %s" % package)
# Doublecheck with apt-mark showhold that packages are indeed held ...
held_packages = check_output("apt-mark showhold").split("\n")
if any(p not in held_packages for p in critical_packages):
logger.warning(m18n.n("tools_upgrade_cant_hold_critical_packages"))
operation_logger.error(m18n.n('packages_upgrade_failed'))
raise YunohostError(m18n.n('packages_upgrade_failed'))
logger.debug("Running apt command :\n{}".format(dist_upgrade))
callbacks = (
lambda l: logger.info(l.rstrip() + "\r"),
lambda l: logger.warning(l.rstrip()),
)
returncode = call_async_output(dist_upgrade, callbacks, shell=True)
if returncode != 0:
logger.warning('tools_upgrade_regular_packages_failed',
packages_list=', '.join(noncritical_packages_upgradable))
operation_logger.error(m18n.n('packages_upgrade_failed'))
raise YunohostError(m18n.n('packages_upgrade_failed'))
#
# Critical packages upgrade
#
if critical_packages_upgradable:
logger.info(m18n.n("tools_upgrade_special_packages"))
# Mark all critical packages as unheld
for package in critical_packages:
check_output("apt-mark unhold %s" % package)
# Doublecheck with apt-mark showhold that packages are indeed unheld ...
held_packages = check_output("apt-mark showhold").split("\n")
if any(p in held_packages for p in critical_packages):
logger.warning(m18n.n("tools_upgrade_cant_unhold_critical_packages"))
operation_logger.error(m18n.n('packages_upgrade_failed'))
raise YunohostError(m18n.n('packages_upgrade_failed'))
#
# Here we use a dirty hack to run a command after the current
# "yunohost tools upgrade", because the upgrade of yunohost
# will also trigger other yunohost commands (e.g. "yunohost tools migrations migrate")
# (also the upgrade of the package, if executed from the webadmin, is
# likely to kill/restart the api which is in turn likely to kill this
# command before it ends...)
#
logfile = operation_logger.log_path
command = dist_upgrade + " 2>&1 | tee -a {}".format(logfile)
MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock"
wait_until_end_of_yunohost_command = "(while [ -f {} ]; do sleep 2; done)".format(MOULINETTE_LOCK)
mark_success = "(echo 'Done!' | tee -a {} && echo 'success: true' >> {})".format(logfile, operation_logger.md_path)
mark_failure = "(echo 'Failed :(' | tee -a {} && echo 'success: false' >> {})".format(logfile, operation_logger.md_path)
update_log_metadata = "sed -i \"s/ended_at: .*$/ended_at: $(date -u +'%Y-%m-%d %H:%M:%S.%N')/\" {}"
update_log_metadata = update_log_metadata.format(operation_logger.md_path)
upgrade_completed = "\n" + m18n.n("tools_upgrade_special_packages_completed")
command = "(({wait} && {cmd}) && {mark_success} || {mark_failure}; {update_metadata}; echo '{done}') &".format(
wait=wait_until_end_of_yunohost_command,
cmd=command,
mark_success=mark_success,
mark_failure=mark_failure,
update_metadata=update_log_metadata,
done=upgrade_completed)
logger.warning(m18n.n("tools_upgrade_special_packages_explanation"))
logger.debug("Running command :\n{}".format(command))
os.system(command)
return
else:
logger.success(m18n.n('system_upgraded'))
operation_logger.success()
def tools_diagnosis(private=False): def tools_diagnosis(private=False):

View file

@ -481,3 +481,46 @@ def dpkg_is_broken():
return False return False
return any(re.match("^[0-9]+$", f) return any(re.match("^[0-9]+$", f)
for f in os.listdir("/var/lib/dpkg/updates/")) for f in os.listdir("/var/lib/dpkg/updates/"))
def _list_upgradable_apt_packages():
from moulinette.utils.process import check_output
# List upgradable packages
# LC_ALL=C is here to make sure the results are in english
upgradable_raw = check_output("LC_ALL=C apt list --upgradable")
# Dirty parsing of the output
upgradable_raw = [l.strip() for l in upgradable_raw.split("\n") if l.strip()]
for line in upgradable_raw:
# Remove stupid warning and verbose messages >.>
if "apt does not have a stable CLI interface" in line or "Listing..." in line:
continue
# line should look like :
# yunohost/stable 3.5.0.2+201903211853 all [upgradable from: 3.4.2.4+201903080053]
line = line.split()
if len(line) != 6:
logger.warning("Failed to parse this line : %s" % ' '.join(line))
continue
yield {
"name": line[0].split("/")[0],
"new_version": line[1],
"current_version": line[5].strip("]"),
}
def _dump_sources_list():
from glob import glob
filenames = glob("/etc/apt/sources.list") + glob("/etc/apt/sources.list.d/*")
for filename in filenames:
with open(filename, "r") as f:
for line in f.readlines():
if line.startswith("#") or not line.strip():
continue
yield filename.replace("/etc/apt/", "") + ":" + line.strip()