Upstream: https://github.com/YunoHost/yunohost @ 3a5f8bac8301c450897b96cbd43a4c7d3ba750fb But (José) : transformer tout le code en bions + ploxions du xerboxion. La carte de digestion vit au labo : /yunohost-digest.json
381 lines
16 KiB
Bash
381 lines
16 KiB
Bash
#!/usr/bin/env bash
|
|
#
|
|
# Copyright (c) 2024 YunoHost Contributors
|
|
#
|
|
# This file is part of YunoHost (see https://yunohost.org)
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
YNH_APT_INSTALL_DEPENDENCIES_REPLACE="true"
|
|
|
|
# Define and install dependencies with a equivs control file
|
|
#
|
|
# example : ynh_apt_install_dependencies dep1 dep2 "dep3|dep4|dep5"
|
|
#
|
|
# usage: ynh_apt_install_dependencies dep [dep [...]]
|
|
# | arg: dep - the package name to install in dependence.
|
|
# | arg: "dep1|dep2|…" - You can specify alternatives. It will require to install (dep1 or dep2, etc).
|
|
#
|
|
ynh_apt_install_dependencies() {
|
|
|
|
# Add a comma for each space between packages. But not add a comma if the space separate a version specification. (See below)
|
|
local dependencies="$(sed 's/\([^\<=\>]\)\ \([^(]\)/\1, \2/g' <<< "$@" | sed 's/|/ | /')"
|
|
local version=$(ynh_read_manifest "version")
|
|
local app_ynh_deps="${app//_/-}-ynh-deps" # Replace all '_' by '-', and append -ynh-deps
|
|
|
|
# Handle specific versions
|
|
if grep '[<=>]' <<< "$dependencies"; then
|
|
# Replace version specifications by relationships syntax
|
|
# https://www.debian.org/doc/debian-policy/ch-relationships.html
|
|
# Sed clarification
|
|
# [^(\<=\>] ignore if it begins by ( or < = >. To not apply twice.
|
|
# [\<=\>] matches < = or >
|
|
# \+ matches one or more occurence of the previous characters, for >= or >>.
|
|
# [^,]\+ matches all characters except ','
|
|
# Ex: 'package>=1.0' will be replaced by 'package (>= 1.0)'
|
|
dependencies="$(sed 's/\([^(\<=\>]\)\([\<=\>]\+\)\([^,]\+\)/\1 (\2 \3)/g' <<< "$dependencies")"
|
|
fi
|
|
|
|
# ############################## #
|
|
# Specific tweaks related to PHP #
|
|
# ############################## #
|
|
|
|
# Check for specific php dependencies which requires sury
|
|
# This grep will for example return "7.4" if dependencies is "foo bar php7.4-pwet php-gni"
|
|
# The (?<=php) syntax corresponds to lookbehind ;)
|
|
local specific_php_version=$(grep -oP '(?<=php)[0-9.]+(?=-|\>|)' <<< "$dependencies" | sort -u)
|
|
|
|
if [[ -n "$specific_php_version" ]]; then
|
|
# Cover a small edge case where a packager could have specified "php7.4-pwet php5-gni" which is confusing
|
|
[[ $(echo "$specific_php_version" | wc -l) -eq 1 ]] \
|
|
|| ynh_die "Inconsistent php versions in dependencies ... found : $specific_php_version"
|
|
|
|
dependencies+=", php${specific_php_version}, php${specific_php_version}-fpm, php${specific_php_version}-common"
|
|
|
|
local old_php_version=$(ynh_app_setting_get --key=php_version)
|
|
|
|
# If the PHP version changed, remove the old fpm conf
|
|
if [ -n "$old_php_version" ] && [ "$old_php_version" != "$specific_php_version" ]; then
|
|
if [[ -f "/etc/php/$php_version/fpm/pool.d/$app.conf" ]]; then
|
|
ynh_backup_if_checksum_is_different "/etc/php/$php_version/fpm/pool.d/$app.conf"
|
|
ynh_config_remove_phpfpm
|
|
fi
|
|
fi
|
|
# Store php_version into the config of this app
|
|
ynh_app_setting_set --key=php_version --value="$specific_php_version"
|
|
|
|
# Set the default php version back as the default version for php-cli.
|
|
if test -e "/usr/bin/php$YNH_DEFAULT_PHP_VERSION"; then
|
|
update-alternatives --set php "/usr/bin/php$YNH_DEFAULT_PHP_VERSION"
|
|
fi
|
|
# Otherwise force php_version back to the default version ...
|
|
# ... except we don't want to do this when appending the "extra" dependencies if they don't contain a php version ...
|
|
elif grep --quiet 'php' <<< "$dependencies" && [[ $YNH_APT_INSTALL_DEPENDENCIES_REPLACE == "true" ]]; then
|
|
ynh_app_setting_set --key=php_version --value="$YNH_DEFAULT_PHP_VERSION"
|
|
fi
|
|
|
|
# Specific tweak related to Postgresql (cf end of the helper)
|
|
local psql_installed="$(_ynh_apt_package_is_installed "postgresql-$PSQL_VERSION" && echo yes || echo no)"
|
|
|
|
# The first time we run ynh_apt_install_dependencies, we will replace the
|
|
# entire control file (This is in particular meant to cover the case of
|
|
# upgrade script where ynh_apt_install_dependencies is called with this
|
|
# expected effect) Otherwise, any subsequent call will add dependencies
|
|
# to those already present in the equivs control file.
|
|
if [[ $YNH_APT_INSTALL_DEPENDENCIES_REPLACE == "true" ]]; then
|
|
YNH_APT_INSTALL_DEPENDENCIES_REPLACE="false"
|
|
else
|
|
local current_dependencies=""
|
|
if _ynh_apt_package_is_installed "${app_ynh_deps}"; then
|
|
current_dependencies="$(dpkg-query --show --showformat='${Depends}' "${app_ynh_deps}") "
|
|
current_dependencies=${current_dependencies// | /|}
|
|
fi
|
|
dependencies="$current_dependencies, $dependencies"
|
|
fi
|
|
|
|
# ################
|
|
# Actual install #
|
|
# ################
|
|
|
|
# Prepare the virtual-dependency control file for dpkg-deb --build
|
|
local TMPDIR=$(mktemp --directory)
|
|
mkdir -p "${TMPDIR}/${app_ynh_deps}/DEBIAN"
|
|
# For some reason, dpkg-deb insists for folder perm to be 755 and sometimes it's 777 o_O?
|
|
chmod -R 755 "${TMPDIR}/${app_ynh_deps}"
|
|
|
|
cat > "${TMPDIR}/${app_ynh_deps}/DEBIAN/control" << EOF
|
|
Section: misc
|
|
Priority: optional
|
|
Package: ${app_ynh_deps}
|
|
Version: ${version}
|
|
Depends: ${dependencies//,,/,}
|
|
Architecture: all
|
|
Maintainer: root@localhost
|
|
Description: Fake package for ${app} (YunoHost app) dependencies
|
|
This meta-package is only responsible of installing its dependencies.
|
|
EOF
|
|
|
|
_ynh_apt update --error-on=any
|
|
|
|
_ynh_wait_dpkg_free
|
|
|
|
# Install the fake package without its dependencies with dpkg --force-depends
|
|
if ! LC_ALL=C dpkg-deb --build "${TMPDIR}/${app_ynh_deps}" "${TMPDIR}/${app_ynh_deps}.deb" > "${TMPDIR}/dpkg_log" 2>&1; then
|
|
cat "${TMPDIR}/dpkg_log" >&2
|
|
ynh_die "Unable to install dependencies"
|
|
fi
|
|
# Don't crash in case of error, because is nicely covered by the following line
|
|
LC_ALL=C dpkg --force-depends --install "${TMPDIR}/${app_ynh_deps}.deb" 2>&1 | tee "${TMPDIR}/dpkg_log" || true
|
|
|
|
# Then install the missing dependencies with apt install
|
|
_ynh_apt_install --fix-broken || {
|
|
# If the installation failed
|
|
# (the following is ran inside { } to not start a subshell otherwise ynh_die wouldnt exit the original process)
|
|
# Parse the list of problematic dependencies from dpkg's log ...
|
|
# (relevant lines look like: "foo-ynh-deps depends on bar; however:")
|
|
cat "$TMPDIR/dpkg_log"
|
|
local problematic_dependencies
|
|
mapfile -t problematic_dependencies < <(grep -oP '(?<=-ynh-deps depends on ).*(?=; however)' "$TMPDIR/dpkg_log")
|
|
# Fake an install of those dependencies to see the errors
|
|
# The sed command here is, Print only from 'Reading state info' to the end.
|
|
if ((${#problematic_dependencies[@]} != 0)); then
|
|
_ynh_apt_install "${problematic_dependencies[@]}" --dry-run 2>&1 | sed --quiet '/Reading state info/,$p' | grep -v "fix-broken\|Reading state info" >&2
|
|
fi
|
|
ynh_die "Unable to install apt dependencies, it might be due to a conflict with another app - or you should check and share the previous log about what are the problematic dependencies"
|
|
}
|
|
rm --recursive --force "$TMPDIR" # Remove the temp dir.
|
|
|
|
# check if the package is actually installed
|
|
_ynh_apt_package_is_installed "${app_ynh_deps}" || ynh_die "Unable to install apt dependencies"
|
|
|
|
# Specific tweak related to Postgresql
|
|
# -> trigger postgresql regenconf if we may have just installed postgresql
|
|
local psql_installed2="$(_ynh_apt_package_is_installed "postgresql-$PSQL_VERSION" && echo yes || echo no)"
|
|
if [[ "$psql_installed" != "$psql_installed2" ]]; then
|
|
yunohost tools regen-conf postgresql
|
|
fi
|
|
|
|
}
|
|
|
|
# Remove fake package and its dependencies
|
|
#
|
|
# Dependencies will removed only if no other package need them.
|
|
#
|
|
# usage: ynh_apt_remove_dependencies
|
|
ynh_apt_remove_dependencies() {
|
|
local app_ynh_deps="${app//_/-}-ynh-deps" # Replace all '_' by '-', and append -ynh-deps
|
|
|
|
local current_dependencies=""
|
|
if _ynh_apt_package_is_installed "${app_ynh_deps}"; then
|
|
current_dependencies="$(dpkg-query --show --showformat='${Depends}' "${app_ynh_deps}") "
|
|
current_dependencies=${current_dependencies// | /|}
|
|
fi
|
|
|
|
# Edge case where the app dep may be on hold,
|
|
# cf https://forum.yunohost.org/t/migration-error-cause-of-ffsync/20675/4
|
|
if apt-mark showhold | grep -q -w "${app_ynh_deps}"; then
|
|
apt-mark unhold "${app_ynh_deps}"
|
|
fi
|
|
|
|
# Remove the fake package and its dependencies if they not still used.
|
|
# (except if dpkg doesn't know anything about the package,
|
|
# which should be symptomatic of a failed install, and we don't want bash to report an error)
|
|
if dpkg-query --show "${app_ynh_deps}" &> /dev/null; then
|
|
_ynh_apt autoremove --purge "${app_ynh_deps}"
|
|
fi
|
|
}
|
|
|
|
# Install packages from an extra repository properly.
|
|
#
|
|
# usage: ynh_apt_install_dependencies_from_extra_repository --repo="repo" --package="dep1 dep2" --key=key_url
|
|
# | arg: --repo= - Complete url of the extra repository.
|
|
# | arg: --package= - The packages to install from this extra repository
|
|
# | arg: --key= - url to get the public key.
|
|
#
|
|
ynh_apt_install_dependencies_from_extra_repository() {
|
|
# ============ Argument parsing =============
|
|
local -A args_array=([r]=repo= [p]=package= [k]=key=)
|
|
local repo
|
|
local package
|
|
local key
|
|
ynh_handle_getopts_args "$@"
|
|
# ===========================================
|
|
|
|
# split package into packages list
|
|
local packages
|
|
read -r -a packages <<< "$package"
|
|
|
|
# Split the repository into uri, suite and components.
|
|
IFS=', ' read -r -a repo_parts <<< "$repo"
|
|
index=0
|
|
|
|
# Remove "deb " at the beginning of the repo.
|
|
if [[ "${repo_parts[0]}" == "deb" ]]; then
|
|
index=1
|
|
fi
|
|
uri="${repo_parts[$index]}"
|
|
index=$((index + 1))
|
|
suite="${repo_parts[$index]}"
|
|
index=$((index + 1))
|
|
|
|
# Get the components
|
|
if (("${#repo_parts[@]}" > 0)); then
|
|
component="${repo_parts[*]:$index}"
|
|
fi
|
|
|
|
if [[ "$key" == "trusted=yes" ]]; then
|
|
trust="[trusted=yes]"
|
|
else
|
|
trust=""
|
|
fi
|
|
|
|
# Add the new repo in sources.list.d
|
|
mkdir --parents "/etc/apt/sources.list.d"
|
|
echo "deb $trust $uri $suite $component" > "/etc/apt/sources.list.d/$app.list"
|
|
|
|
# Pin the new repo with the default priority, so it won't be used for upgrades.
|
|
# Build $pin from the uri without http and any sub path
|
|
local pin="${uri#*://}"
|
|
pin="${pin%%/*}"
|
|
|
|
# Pin repository
|
|
mkdir --parents "/etc/apt/preferences.d"
|
|
cat << EOF > "/etc/apt/preferences.d/$app"
|
|
Package: *
|
|
Pin: origin $pin
|
|
Pin-Priority: 995
|
|
EOF
|
|
|
|
if [ -n "$key" ] && [[ "$key" != "trusted=yes" ]]; then
|
|
mkdir --parents "/etc/apt/trusted.gpg.d"
|
|
# Timeout option is here to enforce the timeout on dns query and tcp connect (c.f. man wget)
|
|
wget --timeout 900 --quiet "$key" --output-document=- | gpg --dearmor > "/etc/apt/trusted.gpg.d/$app.gpg"
|
|
fi
|
|
|
|
# Update the list of package with the new repo NB: we use -o
|
|
# Dir::Etc::sourcelist to only refresh this repo, because
|
|
# ynh_apt_install_dependencies will also call an ynh_apt update on its own
|
|
# and it's good to limit unecessary requests ... Here we mainly want to
|
|
# validate that the url+key is correct before going further
|
|
_ynh_apt update --error-on=any -o Dir::Etc::sourcelist="/etc/apt/sources.list.d/$app.list"
|
|
|
|
# Force the cache to be reupdated on the next "apt update" (in
|
|
# ynh_apt_install_dependencies) because the previous command with
|
|
# ::sourcelist option makes apt forget about every other package for other,
|
|
# so we want to force the cache to be reupdated entirely
|
|
touch "/etc/apt/sources.list.d/$app.list"
|
|
|
|
# Install requested dependencies from this extra repository.
|
|
# NB: because of the mechanism with $ynh_apt_install_DEPENDENCIES_REPLACE,
|
|
# this will usually only *append* to the existing list of dependency, not
|
|
# replace the existing $app-ynh-deps
|
|
ynh_apt_install_dependencies "$package"
|
|
|
|
# Force to upgrade to the last version...
|
|
# Without doing apt install, an already installed dep is not upgraded
|
|
local apps_auto_installed
|
|
mapfile -t apps_auto_installed < <(apt-mark showauto "${packages[@]}")
|
|
_ynh_apt_install "${packages[@]}"
|
|
if ((${#apps_auto_installed[@]} != 0)); then
|
|
apt-mark auto "${apps_auto_installed[@]}"
|
|
fi
|
|
|
|
# Remove this extra repository after packages are installed
|
|
ynh_safe_rm "/etc/apt/sources.list.d/$app.list"
|
|
ynh_safe_rm "/etc/apt/preferences.d/$app"
|
|
ynh_safe_rm "/etc/apt/trusted.gpg.d/$app.gpg"
|
|
_ynh_apt update --error-on=any
|
|
}
|
|
|
|
# #####################
|
|
# Internal misc utils #
|
|
# #####################
|
|
|
|
# Check if apt is free to use, or wait, until timeout.
|
|
_ynh_wait_dpkg_free() {
|
|
local try
|
|
set +o xtrace # set +x
|
|
# With seq 1 17, timeout will be almost 30 minutes
|
|
for try in $(seq 1 17); do
|
|
# Check if /var/lib/dpkg/lock is used by another process
|
|
if lsof /var/lib/dpkg/lock > /dev/null; then
|
|
echo "apt is already in use..."
|
|
# Sleep an exponential time at each round
|
|
sleep $((try * try))
|
|
else
|
|
# Check if dpkg hasn't been interrupted and is fully available.
|
|
# See this for more information: https://sources.debian.org/src/apt/1.4.9/apt-pkg/deb/debsystem.cc/#L141-L174
|
|
local dpkg_dir="/var/lib/dpkg/updates/"
|
|
|
|
# For each file in $dpkg_dir
|
|
while read -r dpkg_file <&9; do
|
|
# Check if the name of this file contains only numbers.
|
|
if echo "$dpkg_file" | grep --perl-regexp --quiet "^[[:digit:]]+$"; then
|
|
# If so, that a remaining of dpkg.
|
|
ynh_print_warn "dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem."
|
|
set -o xtrace # set -x
|
|
return 1
|
|
fi
|
|
done 9<<< "$(ls -1 $dpkg_dir)"
|
|
set -o xtrace # set -x
|
|
return 0
|
|
fi
|
|
done
|
|
echo "apt still used, but timeout reached !"
|
|
set -o xtrace # set -x
|
|
}
|
|
|
|
# Check either a package is installed or not
|
|
_ynh_apt_package_is_installed() {
|
|
local package=$1
|
|
dpkg-query --show --showformat='${db:Status-Status}' "$package" 2> /dev/null \
|
|
| grep --quiet "^installed$" &> /dev/null
|
|
}
|
|
|
|
# Return the installed version of an apt package, if installed
|
|
_ynh_apt_package_version() {
|
|
if _ynh_apt_package_is_installed "$package"; then
|
|
dpkg-query --show --showformat='${Version}' "$package" 2> /dev/null
|
|
else
|
|
echo ''
|
|
fi
|
|
}
|
|
|
|
# APT wrapper for non-interactive operation
|
|
_ynh_apt() {
|
|
|
|
# Optimization when just calling apt update : check if the cache was
|
|
# already refreshed in the last 30 min, which should be enough and prevent
|
|
# unecessary traffic and annoying wait time during app dependency installs etc
|
|
if [[ "$*" == "update" ]]; then
|
|
# trick from https://stackoverflow.com/a/205710
|
|
local aptcache="/var/cache/apt/pkgcache.bin"
|
|
sleep 1
|
|
if [[ -e $aptcache ]] && [[ -n "$(find $aptcache -mmin -30)" ]] && [[ -z "$(find /etc/apt/ -newer $aptcache)" ]]; then
|
|
echo "apt cache was already updated in the last 30 minutes, skipping 'apt update'"
|
|
return
|
|
fi
|
|
fi
|
|
|
|
_ynh_wait_dpkg_free
|
|
LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get --assume-yes --quiet -o=Acquire::Retries=3 -o=Dpkg::Use-Pty=0 "$@"
|
|
}
|
|
|
|
# Wrapper around "apt install" with the appropriate options
|
|
_ynh_apt_install() {
|
|
_ynh_apt --no-remove --option Dpkg::Options::=--force-confdef \
|
|
--option Dpkg::Options::=--force-confold install "$@"
|
|
}
|