mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
[enh] Smarter API self-upgrade mechanics
This commit is contained in:
parent
5a08206837
commit
fff1a8a4d6
3 changed files with 51 additions and 131 deletions
11
debian/postinst
vendored
11
debian/postinst
vendored
|
@ -33,6 +33,17 @@ do_configure() {
|
|||
yunohost diagnosis run --force
|
||||
fi
|
||||
|
||||
# Trick to let yunohost handle the restart of the API,
|
||||
# to prevent the webadmin from cutting the branch it's sitting on
|
||||
if systemctl is-enabled yunohost-api --quiet
|
||||
then
|
||||
if [[ "${YUNOHOST_API_RESTART_WILL_BE_HANDLED_BY_YUNOHOST:-}" != "yes" ]];
|
||||
then
|
||||
systemctl restart yunohost-api
|
||||
else
|
||||
echo "(Delaying the restart of yunohost-api, this should automatically happen after the end of this upgrade)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# summary of how this script can be called:
|
||||
|
|
2
debian/rules
vendored
2
debian/rules
vendored
|
@ -13,7 +13,7 @@ override_dh_auto_build:
|
|||
python3 doc/generate_manpages.py --gzip --output doc/yunohost.8.gz
|
||||
|
||||
override_dh_installinit:
|
||||
dh_installinit -pyunohost --name=yunohost-api --restart-after-upgrade
|
||||
dh_installinit -pyunohost --name=yunohost-api --noscripts
|
||||
dh_installinit -pyunohost --name=yunohost-firewall --noscripts
|
||||
|
||||
override_dh_systemd_enable:
|
||||
|
|
|
@ -531,19 +531,10 @@ def tools_upgrade(
|
|||
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"]
|
||||
|
||||
critical_packages_upgradable = [
|
||||
p["name"] for p in upgradables if p["name"] in critical_packages
|
||||
]
|
||||
noncritical_packages_upgradable = [
|
||||
p["name"] for p in upgradables if p["name"] not in critical_packages
|
||||
]
|
||||
|
||||
# Prepare dist-upgrade command
|
||||
dist_upgrade = "DEBIAN_FRONTEND=noninteractive"
|
||||
if Moulinette.interface.type == "api":
|
||||
dist_upgrade += "YUNOHOST_API_RESTART_WILL_BE_HANDLED_BY_YUNOHOST=yes"
|
||||
dist_upgrade += " APT_LISTCHANGES_FRONTEND=none"
|
||||
dist_upgrade += " apt-get"
|
||||
dist_upgrade += (
|
||||
|
@ -553,136 +544,54 @@ def tools_upgrade(
|
|||
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"))
|
||||
|
||||
logger.info(m18n.n("tools_upgrade_regular_packages"))
|
||||
logger.debug("Running apt command :\n{}".format(dist_upgrade))
|
||||
|
||||
# Mark all critical packages as held
|
||||
for package in critical_packages:
|
||||
check_output("apt-mark hold %s" % package)
|
||||
def is_relevant(line):
|
||||
irrelevants = [
|
||||
"service sudo-ldap already provided",
|
||||
"Reading database ...",
|
||||
]
|
||||
return all(i not in line.rstrip() for i in irrelevants)
|
||||
|
||||
# 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"))
|
||||
callbacks = (
|
||||
lambda l: logger.info("+ " + l.rstrip() + "\r")
|
||||
if is_relevant(l)
|
||||
else logger.debug(l.rstrip() + "\r"),
|
||||
lambda l: logger.warning(l.rstrip())
|
||||
if is_relevant(l)
|
||||
else logger.debug(l.rstrip()),
|
||||
)
|
||||
returncode = call_async_output(dist_upgrade, callbacks, shell=True)
|
||||
|
||||
logger.debug("Running apt command :\n{}".format(dist_upgrade))
|
||||
# If yunohost is being upgraded from the webadmin
|
||||
if "yunohost" in upgradables and Moulinette.interface.type == "api":
|
||||
|
||||
def is_relevant(line):
|
||||
irrelevants = [
|
||||
"service sudo-ldap already provided",
|
||||
"Reading database ...",
|
||||
]
|
||||
return all(i not in line.rstrip() for i in irrelevants)
|
||||
|
||||
callbacks = (
|
||||
lambda l: logger.info("+ " + l.rstrip() + "\r")
|
||||
if is_relevant(l)
|
||||
else logger.debug(l.rstrip() + "\r"),
|
||||
lambda l: logger.warning(l.rstrip())
|
||||
if is_relevant(l)
|
||||
else logger.debug(l.rstrip()),
|
||||
# Restart the API after 10 sec (at now doesn't support sub-minute times...)
|
||||
# We do this so that the API / webadmin still gets the proper HTTP response
|
||||
# It's then up to the webadmin to implement a proper UX process to wait 10 sec and then auto-fresh the webadmin
|
||||
cmd = (
|
||||
"at -M now >/dev/null 2>&1 <<< \"sleep 10; systemctl restart yunohost-api\""
|
||||
)
|
||||
returncode = call_async_output(dist_upgrade, callbacks, shell=True)
|
||||
if returncode != 0:
|
||||
upgradables = list(_list_upgradable_apt_packages())
|
||||
noncritical_packages_upgradable = [
|
||||
p["name"] for p in upgradables if p["name"] not in critical_packages
|
||||
]
|
||||
logger.warning(
|
||||
m18n.n(
|
||||
"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"))
|
||||
# For some reason subprocess doesn't like the redirections so we have to use bash -c explicity...
|
||||
subprocess.check_call(["bash", "-c", cmd])
|
||||
|
||||
#
|
||||
# Critical packages upgrade
|
||||
#
|
||||
if critical_packages_upgradable and allow_yunohost_upgrade:
|
||||
|
||||
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 run")
|
||||
# (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
|
||||
dist_upgrade = 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
|
||||
if returncode != 0:
|
||||
upgradables = list(_list_upgradable_apt_packages())
|
||||
logger.warning(
|
||||
m18n.n(
|
||||
"tools_upgrade_failed",
|
||||
packages_list=", ".join(upgradables),
|
||||
)
|
||||
)
|
||||
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)
|
||||
operation_logger.error(m18n.n("packages_upgrade_failed"))
|
||||
raise YunohostError(m18n.n("packages_upgrade_failed"))
|
||||
|
||||
# Dirty hack such that the operation_logger does not add ended_at
|
||||
# and success keys in the log metadata. (c.f. the code of the
|
||||
# is_unit_operation + operation_logger.close()) We take care of
|
||||
# this ourselves (c.f. the mark_success and updated_log_metadata in
|
||||
# the huge command launched by os.system)
|
||||
operation_logger.ended_at = "notyet"
|
||||
# FIXME : add a dpkg --audit / check dpkg is broken here ?
|
||||
|
||||
upgrade_completed = "\n" + m18n.n(
|
||||
"tools_upgrade_special_packages_completed"
|
||||
)
|
||||
command = "({wait} && {dist_upgrade}) && {mark_success} || {mark_failure}; {update_metadata}; echo '{done}'".format(
|
||||
wait=wait_until_end_of_yunohost_command,
|
||||
dist_upgrade=dist_upgrade,
|
||||
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))
|
||||
open("/tmp/yunohost-selfupgrade", "w").write(
|
||||
"rm /tmp/yunohost-selfupgrade; " + command
|
||||
)
|
||||
# Using systemd-run --scope is like nohup/disown and &, but more robust somehow
|
||||
# (despite using nohup/disown and &, the self-upgrade process was still getting killed...)
|
||||
# ref: https://unix.stackexchange.com/questions/420594/why-process-killed-with-nohup
|
||||
# (though I still don't understand it 100%...)
|
||||
os.system("systemd-run --scope bash /tmp/yunohost-selfupgrade &")
|
||||
return
|
||||
|
||||
else:
|
||||
logger.success(m18n.n("system_upgraded"))
|
||||
operation_logger.success()
|
||||
logger.success(m18n.n("system_upgraded"))
|
||||
operation_logger.success()
|
||||
|
||||
|
||||
@is_unit_operation()
|
||||
|
|
Loading…
Add table
Reference in a new issue