mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge branch 'dev' into permission_protection
This commit is contained in:
commit
a7d105ace6
28 changed files with 786 additions and 346 deletions
|
@ -1,5 +1,9 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Fetch x509 fingerprint
|
||||||
|
x509_fingerprint=$(openssl x509 -in /etc/yunohost/certs/yunohost.org/crt.pem -noout -fingerprint -sha256 | cut -d= -f2)
|
||||||
|
|
||||||
|
|
||||||
# Fetch SSH fingerprints
|
# Fetch SSH fingerprints
|
||||||
i=0
|
i=0
|
||||||
for key in $(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key.pub 2> /dev/null) ; do
|
for key in $(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key.pub 2> /dev/null) ; do
|
||||||
|
@ -40,6 +44,7 @@ LOGO_AND_FINGERPRINTS=$(cat << EOF
|
||||||
$LOGO
|
$LOGO
|
||||||
|
|
||||||
IP: ${local_ip}
|
IP: ${local_ip}
|
||||||
|
X509 fingerprint: ${x509_fingerprint}
|
||||||
SSH fingerprints:
|
SSH fingerprints:
|
||||||
${fingerprint[0]}
|
${fingerprint[0]}
|
||||||
${fingerprint[1]}
|
${fingerprint[1]}
|
||||||
|
|
|
@ -172,7 +172,9 @@ user:
|
||||||
nargs: "*"
|
nargs: "*"
|
||||||
metavar: MAIL
|
metavar: MAIL
|
||||||
extra:
|
extra:
|
||||||
pattern: *pattern_email
|
pattern: &pattern_email_forward
|
||||||
|
- !!str ^[\w\+.-]+@([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$
|
||||||
|
- "pattern_email_forward"
|
||||||
--remove-mailforward:
|
--remove-mailforward:
|
||||||
help: Mailforward addresses to remove
|
help: Mailforward addresses to remove
|
||||||
nargs: "*"
|
nargs: "*"
|
||||||
|
@ -918,6 +920,14 @@ backup:
|
||||||
help: Print sizes in human readable format
|
help: Print sizes in human readable format
|
||||||
action: store_true
|
action: store_true
|
||||||
|
|
||||||
|
### backup_download()
|
||||||
|
download:
|
||||||
|
action_help: (API only) Request to download the file
|
||||||
|
api: GET /backup/download/<name>
|
||||||
|
arguments:
|
||||||
|
name:
|
||||||
|
help: Name of the local backup archive
|
||||||
|
|
||||||
### backup_delete()
|
### backup_delete()
|
||||||
delete:
|
delete:
|
||||||
action_help: Delete a backup archive
|
action_help: Delete a backup archive
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# -*- shell-script -*-
|
# -*- shell-script -*-
|
||||||
|
|
||||||
|
readonly XTRACE_ENABLE=$(set +o | grep xtrace) # This is a trick to later only restore set -x if it was set when calling this script
|
||||||
set +x
|
set +x
|
||||||
for helper in $(run-parts --list /usr/share/yunohost/helpers.d 2>/dev/null) ; do
|
for helper in $(run-parts --list /usr/share/yunohost/helpers.d 2>/dev/null) ; do
|
||||||
[ -r $helper ] && . $helper || true
|
[ -r $helper ] && . $helper || true
|
||||||
done
|
done
|
||||||
set -x
|
eval "$XTRACE_BKP"
|
||||||
|
|
||||||
|
|
|
@ -191,17 +191,17 @@ ynh_package_install_from_equivs () {
|
||||||
cp "$controlfile" "${TMPDIR}/control"
|
cp "$controlfile" "${TMPDIR}/control"
|
||||||
(cd "$TMPDIR"
|
(cd "$TMPDIR"
|
||||||
LC_ALL=C equivs-build ./control 1> /dev/null
|
LC_ALL=C equivs-build ./control 1> /dev/null
|
||||||
dpkg --force-depends --install "./${pkgname}_${pkgversion}_all.deb" 2>&1)
|
LC_ALL=C dpkg --force-depends --install "./${pkgname}_${pkgversion}_all.deb" 2>&1 | tee ./dpkg_log)
|
||||||
|
|
||||||
ynh_package_install --fix-broken || \
|
ynh_package_install --fix-broken || \
|
||||||
{ # If the installation failed
|
{ # If the installation failed
|
||||||
# (the following is ran inside { } to not start a subshell otherwise ynh_die wouldnt exit the original process)
|
# (the following is ran inside { } to not start a subshell otherwise ynh_die wouldnt exit the original process)
|
||||||
# Get the list of dependencies from the deb
|
# Parse the list of problematic dependencies from dpkg's log ...
|
||||||
local dependencies="$(dpkg --info "$TMPDIR/${pkgname}_${pkgversion}_all.deb" | grep Depends | \
|
# (relevant lines look like: "foo-ynh-deps depends on bar; however:")
|
||||||
sed 's/^ Depends: //' | sed 's/,//g')"
|
local problematic_dependencies="$(cat $TMPDIR/dpkg_log | grep -oP '(?<=-ynh-deps depends on ).*(?=; however)' | tr '\n' ' ')"
|
||||||
# Fake an install of those dependencies to see the errors
|
# Fake an install of those dependencies to see the errors
|
||||||
# The sed command here is, Print only from '--fix-broken' to the end.
|
# The sed command here is, Print only from 'Reading state info' to the end.
|
||||||
ynh_package_install $dependencies --dry-run | sed --quiet '/--fix-broken/,$p' >&2
|
[[ -n "$problematic_dependencies" ]] && ynh_package_install $problematic_dependencies --dry-run 2>&1 | sed --quiet '/Reading state info/,$p' | grep -v "fix-broken\|Reading state info" >&2
|
||||||
ynh_die --message="Unable to install dependencies"; }
|
ynh_die --message="Unable to install dependencies"; }
|
||||||
[[ -n "$TMPDIR" ]] && rm --recursive --force $TMPDIR # Remove the temp dir.
|
[[ -n "$TMPDIR" ]] && rm --recursive --force $TMPDIR # Remove the temp dir.
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,24 @@ CAN_BIND=${CAN_BIND:-1}
|
||||||
# ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" "/conf/"
|
# ynh_backup "/etc/nginx/conf.d/$domain.d/$app.conf" "/conf/"
|
||||||
# # => "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/conf/$app.conf"
|
# # => "/etc/nginx/conf.d/$domain.d/$app.conf","apps/wordpress/conf/$app.conf"
|
||||||
#
|
#
|
||||||
|
#
|
||||||
|
# How to use --is_big:
|
||||||
|
# --is_big is used to specify that this part of the backup can be quite huge.
|
||||||
|
# So, you don't want that your package does backup that part during ynh_backup_before_upgrade.
|
||||||
|
# In the same way, an user may doesn't want to backup this big part of the app for
|
||||||
|
# each of his backup. And so handle that part differently.
|
||||||
|
#
|
||||||
|
# As this part of your backup may not be done, your restore script has to handle it.
|
||||||
|
# In your restore script, use --not_mandatory with ynh_restore_file
|
||||||
|
# As well in your remove script, you should not remove those data ! Or an user may end up with
|
||||||
|
# a failed upgrade restoring an app without data anymore !
|
||||||
|
#
|
||||||
|
# To have the benefit of --is_big while doing a backup, you can whether set the environement
|
||||||
|
# variable BACKUP_CORE_ONLY to 1 (BACKUP_CORE_ONLY=1) before the backup command. It will affect
|
||||||
|
# only that backup command.
|
||||||
|
# Or set the config do_not_backup_data to 1 into the settings.yml of the app. This will affect
|
||||||
|
# all backups for this app until the setting is removed.
|
||||||
|
#
|
||||||
# Requires YunoHost version 2.4.0 or higher.
|
# Requires YunoHost version 2.4.0 or higher.
|
||||||
# Requires YunoHost version 3.5.0 or higher for the argument --not_mandatory
|
# Requires YunoHost version 3.5.0 or higher for the argument --not_mandatory
|
||||||
ynh_backup() {
|
ynh_backup() {
|
||||||
|
@ -67,9 +85,9 @@ ynh_backup() {
|
||||||
then
|
then
|
||||||
if [ $BACKUP_CORE_ONLY -eq 1 ]
|
if [ $BACKUP_CORE_ONLY -eq 1 ]
|
||||||
then
|
then
|
||||||
ynh_print_warn --message="$src_path will not be saved, because 'BACKUP_CORE_ONLY' is set."
|
ynh_print_info --message="$src_path will not be saved, because 'BACKUP_CORE_ONLY' is set."
|
||||||
else
|
else
|
||||||
ynh_print_warn --message="$src_path will not be saved, because 'do_not_backup_data' is set."
|
ynh_print_info --message="$src_path will not be saved, because 'do_not_backup_data' is set."
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -130,6 +130,12 @@ ynh_handle_getopts_args () {
|
||||||
then
|
then
|
||||||
# Remove the option and the space, so keep only the value itself.
|
# Remove the option and the space, so keep only the value itself.
|
||||||
all_args[0]="${all_args[0]#-${parameter} }"
|
all_args[0]="${all_args[0]#-${parameter} }"
|
||||||
|
|
||||||
|
# At this point, if all_args[0] start with "-", then the argument is not well formed
|
||||||
|
if [ "${all_args[0]:0:1}" == "-" ]
|
||||||
|
then
|
||||||
|
ynh_die --message="Argument \"${all_args[0]}\" not valid! Did you use a single \"-\" instead of two?"
|
||||||
|
fi
|
||||||
# Reduce the value of shift, because the option has been removed manually
|
# Reduce the value of shift, because the option has been removed manually
|
||||||
shift_value=$(( shift_value - 1 ))
|
shift_value=$(( shift_value - 1 ))
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -111,12 +111,6 @@ ynh_add_fpm_config () {
|
||||||
local fpm_service="php${phpversion}-fpm"
|
local fpm_service="php${phpversion}-fpm"
|
||||||
local fpm_config_dir="/etc/php/$phpversion/fpm"
|
local fpm_config_dir="/etc/php/$phpversion/fpm"
|
||||||
fi
|
fi
|
||||||
# Configure PHP-FPM 5 on Debian Jessie
|
|
||||||
if [ "$(ynh_get_debian_release)" == "jessie" ]
|
|
||||||
then
|
|
||||||
fpm_config_dir="/etc/php5/fpm"
|
|
||||||
fpm_service="php5-fpm"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create the directory for FPM pools
|
# Create the directory for FPM pools
|
||||||
mkdir --parents "$fpm_config_dir/pool.d"
|
mkdir --parents "$fpm_config_dir/pool.d"
|
||||||
|
@ -219,7 +213,7 @@ ynh_add_fpm_config () {
|
||||||
|
|
||||||
if [ -e "../conf/php-fpm.ini" ]
|
if [ -e "../conf/php-fpm.ini" ]
|
||||||
then
|
then
|
||||||
ynh_print_warn -message="Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead."
|
ynh_print_warn --message="Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead."
|
||||||
finalphpini="$fpm_config_dir/conf.d/20-$app.ini"
|
finalphpini="$fpm_config_dir/conf.d/20-$app.ini"
|
||||||
ynh_backup_if_checksum_is_different "$finalphpini"
|
ynh_backup_if_checksum_is_different "$finalphpini"
|
||||||
cp ../conf/php-fpm.ini "$finalphpini"
|
cp ../conf/php-fpm.ini "$finalphpini"
|
||||||
|
|
|
@ -263,6 +263,149 @@ ynh_local_curl () {
|
||||||
curl --silent --show-error --insecure --location --header "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" --cookie-jar $cookiefile --cookie $cookiefile
|
curl --silent --show-error --insecure --location --header "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" --cookie-jar $cookiefile --cookie $cookiefile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Create a dedicated config file from a template
|
||||||
|
#
|
||||||
|
# examples:
|
||||||
|
# ynh_add_config --template=".env" --destination="$final_path/.env"
|
||||||
|
# ynh_add_config --template="../conf/.env" --destination="$final_path/.env"
|
||||||
|
# ynh_add_config --template="/etc/nginx/sites-available/default" --destination="etc/nginx/sites-available/mydomain.conf"
|
||||||
|
#
|
||||||
|
# usage: ynh_add_config --template="template" --destination="destination"
|
||||||
|
# | arg: -t, --template= - Template config file to use
|
||||||
|
# | arg: -d, --destination= - Destination of the config file
|
||||||
|
#
|
||||||
|
# The template can be by default the name of a file in the conf directory
|
||||||
|
# of a YunoHost Package, a relative path or an absolute path
|
||||||
|
# The helper will use the template $template to generate a config file
|
||||||
|
# $destination by replacing the following keywords with global variables
|
||||||
|
# that should be defined before calling this helper :
|
||||||
|
# __PATH__ by $path_url
|
||||||
|
# __NAME__ by $app
|
||||||
|
# __NAMETOCHANGE__ by $app
|
||||||
|
# __USER__ by $app
|
||||||
|
# __FINALPATH__ by $final_path
|
||||||
|
# __PHPVERSION__ by $YNH_PHP_VERSION
|
||||||
|
# __YNH_NODE_LOAD_PATH__ by $ynh_node_load_PATH
|
||||||
|
#
|
||||||
|
# And any dynamic variables that should be defined before calling this helper like:
|
||||||
|
# __DOMAIN__ by $domain
|
||||||
|
# __APP__ by $app
|
||||||
|
# __VAR_1__ by $var_1
|
||||||
|
# __VAR_2__ by $var_2
|
||||||
|
#
|
||||||
|
# The helper will verify the checksum and backup the destination file
|
||||||
|
# if it's different before applying the new template.
|
||||||
|
# And it will calculate and store the destination file checksum
|
||||||
|
# into the app settings when configuration is done.
|
||||||
|
#
|
||||||
|
# Requires YunoHost version 4.1.0 or higher.
|
||||||
|
ynh_add_config () {
|
||||||
|
# Declare an array to define the options of this helper.
|
||||||
|
local legacy_args=tdv
|
||||||
|
local -A args_array=( [t]=template= [d]=destination= )
|
||||||
|
local template
|
||||||
|
local destination
|
||||||
|
# Manage arguments with getopts
|
||||||
|
ynh_handle_getopts_args "$@"
|
||||||
|
local template_path
|
||||||
|
|
||||||
|
if [ -f "../conf/$template" ]; then
|
||||||
|
template_path="../conf/$template"
|
||||||
|
elif [ -f "../settings/conf/$template" ]; then
|
||||||
|
template_path="../settings/conf/$template"
|
||||||
|
elif [ -f "$template" ]; then
|
||||||
|
template_path=$template
|
||||||
|
else
|
||||||
|
ynh_die --message="The provided template $template doesn't exist"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ynh_backup_if_checksum_is_different --file="$destination"
|
||||||
|
|
||||||
|
cp "$template_path" "$destination"
|
||||||
|
|
||||||
|
ynh_replace_vars --file="$destination"
|
||||||
|
|
||||||
|
ynh_store_file_checksum --file="$destination"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Replace variables in a file
|
||||||
|
#
|
||||||
|
# [internal]
|
||||||
|
#
|
||||||
|
# usage: ynh_replace_vars --file="file"
|
||||||
|
# | arg: -f, --file= - File where to replace variables
|
||||||
|
#
|
||||||
|
# The helper will replace the following keywords with global variables
|
||||||
|
# that should be defined before calling this helper :
|
||||||
|
# __PATH__ by $path_url
|
||||||
|
# __NAME__ by $app
|
||||||
|
# __NAMETOCHANGE__ by $app
|
||||||
|
# __USER__ by $app
|
||||||
|
# __FINALPATH__ by $final_path
|
||||||
|
# __PHPVERSION__ by $YNH_PHP_VERSION
|
||||||
|
# __YNH_NODE_LOAD_PATH__ by $ynh_node_load_PATH
|
||||||
|
#
|
||||||
|
# And any dynamic variables that should be defined before calling this helper like:
|
||||||
|
# __DOMAIN__ by $domain
|
||||||
|
# __APP__ by $app
|
||||||
|
# __VAR_1__ by $var_1
|
||||||
|
# __VAR_2__ by $var_2
|
||||||
|
#
|
||||||
|
# Requires YunoHost version 4.1.0 or higher.
|
||||||
|
ynh_replace_vars () {
|
||||||
|
# Declare an array to define the options of this helper.
|
||||||
|
local legacy_args=f
|
||||||
|
local -A args_array=( [f]=file= )
|
||||||
|
local file
|
||||||
|
# Manage arguments with getopts
|
||||||
|
ynh_handle_getopts_args "$@"
|
||||||
|
|
||||||
|
# Replace specific YunoHost variables
|
||||||
|
if test -n "${path_url:-}"
|
||||||
|
then
|
||||||
|
# path_url_slash_less is path_url, or a blank value if path_url is only '/'
|
||||||
|
local path_url_slash_less=${path_url%/}
|
||||||
|
ynh_replace_string --match_string="__PATH__/" --replace_string="$path_url_slash_less/" --target_file="$file"
|
||||||
|
ynh_replace_string --match_string="__PATH__" --replace_string="$path_url" --target_file="$file"
|
||||||
|
fi
|
||||||
|
if test -n "${app:-}"; then
|
||||||
|
ynh_replace_string --match_string="__NAME__" --replace_string="$app" --target_file="$file"
|
||||||
|
ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$file"
|
||||||
|
ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$file"
|
||||||
|
fi
|
||||||
|
if test -n "${final_path:-}"; then
|
||||||
|
ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$file"
|
||||||
|
fi
|
||||||
|
if test -n "${YNH_PHP_VERSION:-}"; then
|
||||||
|
ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$YNH_PHP_VERSION" --target_file="$file"
|
||||||
|
fi
|
||||||
|
if test -n "${ynh_node_load_PATH:-}"; then
|
||||||
|
ynh_replace_string --match_string="__YNH_NODE_LOAD_PATH__" --replace_string="$ynh_node_load_PATH" --target_file="$file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Replace others variables
|
||||||
|
|
||||||
|
# List other unique (__ __) variables in $file
|
||||||
|
local uniques_vars=( $(grep -o '__[A-Z0-9_]*__' $file | sort --unique | sed "s@__\([^.]*\)__@\L\1@g" ))
|
||||||
|
|
||||||
|
# Do the replacement
|
||||||
|
local delimit=@
|
||||||
|
for one_var in "${uniques_vars[@]}"
|
||||||
|
do
|
||||||
|
# Validate that one_var is indeed defined
|
||||||
|
test -n "${!one_var:-}" || ynh_die --message="\$$one_var wasn't initialized when trying to replace __${one_var^^}__ in $file"
|
||||||
|
|
||||||
|
# Escape delimiter in match/replace string
|
||||||
|
match_string="__${one_var^^}__"
|
||||||
|
match_string=${match_string//${delimit}/"\\${delimit}"}
|
||||||
|
replace_string="${!one_var}"
|
||||||
|
replace_string=${replace_string//${delimit}/"\\${delimit}"}
|
||||||
|
|
||||||
|
# Actually replace (sed is used instead of ynh_replace_string to avoid triggering an epic amount of debug logs)
|
||||||
|
sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$file"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
# Render templates with Jinja2
|
# Render templates with Jinja2
|
||||||
#
|
#
|
||||||
# [internal]
|
# [internal]
|
||||||
|
@ -417,7 +560,7 @@ ynh_read_manifest () {
|
||||||
jq ".$manifest_key" "$manifest" --raw-output
|
jq ".$manifest_key" "$manifest" --raw-output
|
||||||
}
|
}
|
||||||
|
|
||||||
# Read the upstream version from the manifest
|
# Read the upstream version from the manifest, or from the env variable $YNH_APP_MANIFEST_VERSION if not given
|
||||||
#
|
#
|
||||||
# usage: ynh_app_upstream_version [--manifest="manifest.json"]
|
# usage: ynh_app_upstream_version [--manifest="manifest.json"]
|
||||||
# | arg: -m, --manifest= - Path of the manifest to read
|
# | arg: -m, --manifest= - Path of the manifest to read
|
||||||
|
@ -437,7 +580,13 @@ ynh_app_upstream_version () {
|
||||||
# Manage arguments with getopts
|
# Manage arguments with getopts
|
||||||
ynh_handle_getopts_args "$@"
|
ynh_handle_getopts_args "$@"
|
||||||
|
|
||||||
version_key=$YNH_APP_MANIFEST_VERSION
|
if [[ "$manifest" != "" ]] && [[ -e "$manifest" ]];
|
||||||
|
then
|
||||||
|
version_key=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version")
|
||||||
|
else
|
||||||
|
version_key=$YNH_APP_MANIFEST_VERSION
|
||||||
|
fi
|
||||||
|
|
||||||
echo "${version_key/~ynh*/}"
|
echo "${version_key/~ynh*/}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,17 +7,21 @@ do_pre_regen() {
|
||||||
|
|
||||||
mkdir --parents "${pending_dir}/etc/apt/preferences.d"
|
mkdir --parents "${pending_dir}/etc/apt/preferences.d"
|
||||||
|
|
||||||
for package in "php" "php-fpm" "php-mysql" "php-xml" "php-zip" "php-mbstring" "php-ldap" "php-gd" "php-curl" "php-bz2" "php-json" "php-sqlite3" "php-intl" "openssl" "libssl1.1" "libssl-dev"
|
packages_to_refuse_from_sury="php php-fpm php-mysql php-xml php-zip php-mbstring php-ldap php-gd php-curl php-bz2 php-json php-sqlite3 php-intl openssl libssl1.1 libssl-dev"
|
||||||
|
for package in $packages_to_refuse_from_sury
|
||||||
do
|
do
|
||||||
echo "
|
echo "
|
||||||
Package: $package
|
Package: $package
|
||||||
Pin: origin \"packages.sury.org\"
|
Pin: origin \"packages.sury.org\"
|
||||||
Pin-Priority: -1" >> "/etc/apt/preferences.d/extra_php_version"
|
Pin-Priority: -1" >> "${pending_dir}/etc/apt/preferences.d/extra_php_version"
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
do_post_regen() {
|
do_post_regen() {
|
||||||
regen_conf_files=$1
|
regen_conf_files=$1
|
||||||
|
|
||||||
|
# Make sure php7.3 is the default version when using php in cli
|
||||||
|
update-alternatives --set php /usr/bin/php7.3
|
||||||
}
|
}
|
||||||
|
|
||||||
FORCE=${2:-0}
|
FORCE=${2:-0}
|
||||||
|
|
|
@ -24,6 +24,25 @@ do_pre_regen() {
|
||||||
# Support different strategy for security configurations
|
# Support different strategy for security configurations
|
||||||
export compatibility="$(yunohost settings get 'security.postfix.compatibility')"
|
export compatibility="$(yunohost settings get 'security.postfix.compatibility')"
|
||||||
|
|
||||||
|
# Add possibility to specify a relay
|
||||||
|
# Could be useful with some isp with no 25 port open or more complex setup
|
||||||
|
export relay_host="$(yunohost settings get 'smtp.relay.host')"
|
||||||
|
if [ -n "${relay_host}" ]
|
||||||
|
then
|
||||||
|
export relay_port="$(yunohost settings get 'smtp.relay.port')"
|
||||||
|
export relay_user="$(yunohost settings get 'smtp.relay.user')"
|
||||||
|
relay_password="$(yunohost settings get 'smtp.relay.password')"
|
||||||
|
|
||||||
|
# Avoid to display "Relay account paswword" to other users
|
||||||
|
touch ${postfix_dir}/sasl_passwd
|
||||||
|
chmod 750 ${postfix_dir}/sasl_passwd
|
||||||
|
# Avoid "postmap: warning: removing zero-length database file"
|
||||||
|
chown postfix ${pending_dir}/etc/postfix
|
||||||
|
chown postfix ${pending_dir}/etc/postfix/sasl_passwd
|
||||||
|
|
||||||
|
cat <<< "[${relay_host}]:${relay_port} ${relay_user}:${relay_password}" > ${postfix_dir}/sasl_passwd
|
||||||
|
postmap ${postfix_dir}/sasl_passwd
|
||||||
|
fi
|
||||||
export main_domain
|
export main_domain
|
||||||
export domain_list="$YNH_DOMAINS"
|
export domain_list="$YNH_DOMAINS"
|
||||||
ynh_render_template "main.cf" "${postfix_dir}/main.cf"
|
ynh_render_template "main.cf" "${postfix_dir}/main.cf"
|
||||||
|
@ -48,6 +67,12 @@ do_pre_regen() {
|
||||||
do_post_regen() {
|
do_post_regen() {
|
||||||
regen_conf_files=$1
|
regen_conf_files=$1
|
||||||
|
|
||||||
|
if [ -e /etc/postfix/sasl_passwd ]
|
||||||
|
then
|
||||||
|
chmod 750 /etc/postfix/sasl_passwd*
|
||||||
|
chown postfix:root /etc/postfix/sasl_passwd*
|
||||||
|
fi
|
||||||
|
|
||||||
[[ -z "$regen_conf_files" ]] \
|
[[ -z "$regen_conf_files" ]] \
|
||||||
|| { service postfix restart && service postsrsd restart; }
|
|| { service postfix restart && service postsrsd restart; }
|
||||||
|
|
||||||
|
|
28
data/hooks/conf_regen/35-redis
Executable file
28
data/hooks/conf_regen/35-redis
Executable file
|
@ -0,0 +1,28 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
do_pre_regen() {
|
||||||
|
}
|
||||||
|
|
||||||
|
do_post_regen() {
|
||||||
|
# Enforce these damn permissions because for some reason in some weird cases
|
||||||
|
# they are spontaneously replaced by root:root -_-
|
||||||
|
chown -R redis:adm /var/log/redis
|
||||||
|
}
|
||||||
|
|
||||||
|
FORCE=${2:-0}
|
||||||
|
DRY_RUN=${3:-0}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
pre)
|
||||||
|
do_pre_regen $4
|
||||||
|
;;
|
||||||
|
post)
|
||||||
|
do_post_regen $4
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "hook called with unknown argument \`$1'" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
|
@ -82,6 +82,29 @@ class BaseSystemDiagnoser(Diagnoser):
|
||||||
details=["diagnosis_security_vulnerable_to_meltdown_details"]
|
details=["diagnosis_security_vulnerable_to_meltdown_details"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
bad_sury_packages = list(self.bad_sury_packages())
|
||||||
|
if bad_sury_packages:
|
||||||
|
cmd_to_fix = "apt install --allow-downgrades " \
|
||||||
|
+ " ".join(["%s=%s" % (package, version) for package, version in bad_sury_packages])
|
||||||
|
yield dict(meta={"test": "packages_from_sury"},
|
||||||
|
data={"cmd_to_fix": cmd_to_fix},
|
||||||
|
status="WARNING",
|
||||||
|
summary="diagnosis_package_installed_from_sury",
|
||||||
|
details=["diagnosis_package_installed_from_sury_details"])
|
||||||
|
|
||||||
|
def bad_sury_packages(self):
|
||||||
|
|
||||||
|
packages_to_check = ["openssl", "libssl1.1", "libssl-dev"]
|
||||||
|
for package in packages_to_check:
|
||||||
|
cmd = "dpkg --list | grep '^ii' | grep gbp | grep -q -w %s" % package
|
||||||
|
# If version currently installed is not from sury, nothing to report
|
||||||
|
if os.system(cmd) != 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
cmd = "LC_ALL=C apt policy %s 2>&1 | grep http -B1 | tr -d '*' | grep '+deb' | grep -v 'gbp' | head -n 1 | awk '{print $1}'" % package
|
||||||
|
version_to_downgrade_to = check_output(cmd).strip()
|
||||||
|
yield (package, version_to_downgrade_to)
|
||||||
|
|
||||||
def is_vulnerable_to_meltdown(self):
|
def is_vulnerable_to_meltdown(self):
|
||||||
# meltdown CVE: https://security-tracker.debian.org/tracker/CVE-2017-5754
|
# meltdown CVE: https://security-tracker.debian.org/tracker/CVE-2017-5754
|
||||||
|
|
||||||
|
|
|
@ -53,8 +53,12 @@ smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
|
||||||
smtpd_tls_loglevel=1
|
smtpd_tls_loglevel=1
|
||||||
|
|
||||||
# -- TLS for outgoing connections
|
# -- TLS for outgoing connections
|
||||||
|
{% if relay_host != "" %}
|
||||||
|
smtp_tls_security_level = encrypt
|
||||||
|
{% else %}
|
||||||
# Use TLS if this is supported by the remote SMTP server, otherwise use plaintext.
|
# Use TLS if this is supported by the remote SMTP server, otherwise use plaintext.
|
||||||
smtp_tls_security_level=may
|
smtp_tls_security_level = may
|
||||||
|
{% endif %}
|
||||||
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
|
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
|
||||||
smtp_tls_exclude_ciphers = aNULL, MD5, DES, ADH, RC4, 3DES
|
smtp_tls_exclude_ciphers = aNULL, MD5, DES, ADH, RC4, 3DES
|
||||||
smtp_tls_mandatory_ciphers= high
|
smtp_tls_mandatory_ciphers= high
|
||||||
|
@ -73,7 +77,11 @@ alias_maps = hash:/etc/aliases
|
||||||
alias_database = hash:/etc/aliases
|
alias_database = hash:/etc/aliases
|
||||||
mydomain = {{ main_domain }}
|
mydomain = {{ main_domain }}
|
||||||
mydestination = localhost
|
mydestination = localhost
|
||||||
|
{% if relay_host == "" %}
|
||||||
relayhost =
|
relayhost =
|
||||||
|
{% else %}
|
||||||
|
relayhost = [{{ relay_host }}]:{{ relay_port }}
|
||||||
|
{% endif %}
|
||||||
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
|
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
|
||||||
mailbox_command = procmail -a "$EXTENSION"
|
mailbox_command = procmail -a "$EXTENSION"
|
||||||
mailbox_size_limit = 0
|
mailbox_size_limit = 0
|
||||||
|
@ -179,3 +187,13 @@ default_destination_rate_delay = 5s
|
||||||
# So it's easly possible to scan a server to know which email adress is valid
|
# So it's easly possible to scan a server to know which email adress is valid
|
||||||
# and after to send spam
|
# and after to send spam
|
||||||
disable_vrfy_command = yes
|
disable_vrfy_command = yes
|
||||||
|
|
||||||
|
{% if relay_user != "" %}
|
||||||
|
# Relay email through an other smtp account
|
||||||
|
# enable SASL authentication
|
||||||
|
smtp_sasl_auth_enable = yes
|
||||||
|
# disallow methods that allow anonymous authentication.
|
||||||
|
smtp_sasl_security_options = noanonymous
|
||||||
|
# where to find sasl_passwd
|
||||||
|
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
|
||||||
|
{% endif %}
|
||||||
|
|
|
@ -66,12 +66,19 @@ AcceptEnv LANG LC_*
|
||||||
|
|
||||||
# SFTP stuff
|
# SFTP stuff
|
||||||
Subsystem sftp internal-sftp
|
Subsystem sftp internal-sftp
|
||||||
Match User sftpusers
|
|
||||||
ForceCommand internal-sftp
|
# Forbid users from using their account SSH as a VPN (even if SSH login is disabled)
|
||||||
ChrootDirectory /home/%u
|
AllowTcpForwarding no
|
||||||
AllowTcpForwarding no
|
AllowStreamLocalForwarding no
|
||||||
GatewayPorts no
|
|
||||||
X11Forwarding no
|
# Disable .ssh/rc, which could be edited (e.g. from Nextcloud or whatever) by users to execute arbitrary commands even if SSH login is disabled
|
||||||
|
PermitUserRC no
|
||||||
|
|
||||||
|
Match User admin,root
|
||||||
|
AllowTcpForwarding yes
|
||||||
|
AllowStreamLocalForwarding yes
|
||||||
|
PermitUserRC yes
|
||||||
|
|
||||||
|
|
||||||
# root login is allowed on local networks
|
# root login is allowed on local networks
|
||||||
# It's meant to be a backup solution in case LDAP is down and
|
# It's meant to be a backup solution in case LDAP is down and
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
avahi-daemon: {}
|
avahi-daemon: {}
|
||||||
dnsmasq: {}
|
dnsmasq:
|
||||||
|
test_conf: dnsmasq --test
|
||||||
dovecot:
|
dovecot:
|
||||||
log: [/var/log/mail.log,/var/log/mail.err]
|
log: [/var/log/mail.log,/var/log/mail.err]
|
||||||
needs_exposed_ports: [993]
|
needs_exposed_ports: [993]
|
||||||
|
@ -7,6 +8,7 @@ dovecot:
|
||||||
fail2ban:
|
fail2ban:
|
||||||
log: /var/log/fail2ban.log
|
log: /var/log/fail2ban.log
|
||||||
category: security
|
category: security
|
||||||
|
test_conf: fail2ban-server --test
|
||||||
metronome:
|
metronome:
|
||||||
log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err]
|
log: [/var/log/metronome/metronome.log,/var/log/metronome/metronome.err]
|
||||||
needs_exposed_ports: [5222, 5269]
|
needs_exposed_ports: [5222, 5269]
|
||||||
|
@ -37,6 +39,7 @@ rspamd:
|
||||||
category: email
|
category: email
|
||||||
slapd:
|
slapd:
|
||||||
category: database
|
category: database
|
||||||
|
test_conf: slapd -Tt
|
||||||
ssh:
|
ssh:
|
||||||
log: /var/log/auth.log
|
log: /var/log/auth.log
|
||||||
test_conf: sshd -t
|
test_conf: sshd -t
|
||||||
|
|
|
@ -184,7 +184,7 @@
|
||||||
"domain_cannot_remove_main": "Die primäre Domain konnten nicht entfernt werden. Lege zuerst einen neue primäre Domain fest",
|
"domain_cannot_remove_main": "Die primäre Domain konnten nicht entfernt werden. Lege zuerst einen neue primäre Domain fest",
|
||||||
"certmanager_self_ca_conf_file_not_found": "Die Konfigurationsdatei der Zertifizierungsstelle für selbstsignierte Zertifikate wurde nicht gefunden (Datei {file:s})",
|
"certmanager_self_ca_conf_file_not_found": "Die Konfigurationsdatei der Zertifizierungsstelle für selbstsignierte Zertifikate wurde nicht gefunden (Datei {file:s})",
|
||||||
"certmanager_acme_not_configured_for_domain": "Die ACME Challenge kann im Moment nicht für {domain} ausgeführt werden, weil in ihrer nginx conf das entsprechende Code-Snippet fehlt... Bitte stellen Sie sicher, dass Ihre nginx-Konfiguration mit 'yunohost tools regen-conf nginx --dry-run --with-diff' auf dem neuesten Stand ist.",
|
"certmanager_acme_not_configured_for_domain": "Die ACME Challenge kann im Moment nicht für {domain} ausgeführt werden, weil in ihrer nginx conf das entsprechende Code-Snippet fehlt... Bitte stellen Sie sicher, dass Ihre nginx-Konfiguration mit 'yunohost tools regen-conf nginx --dry-run --with-diff' auf dem neuesten Stand ist.",
|
||||||
"certmanager_unable_to_parse_self_CA_name": "Der Name der Zertifizierungsstelle für selbstsignierte Zertifikate konnte nicht analysiert werden (Datei: {file:s})",
|
"certmanager_unable_to_parse_self_CA_name": "Der Name der Zertifizierungsstelle für selbstsignierte Zertifikate konnte nicht aufgelöst werden (Datei: {file:s})",
|
||||||
"certmanager_http_check_timeout": "Eine Zeitüberschreitung ist aufgetreten, als der Server versuchte sich selbst über HTTP mit der öffentlichen IP (Domain '{domain:s}' mit der IP '{ip:s}') zu erreichen. Möglicherweise ist dafür hairpinning oder eine falsch konfigurierte Firewall/Router deines Servers dafür verantwortlich.",
|
"certmanager_http_check_timeout": "Eine Zeitüberschreitung ist aufgetreten, als der Server versuchte sich selbst über HTTP mit der öffentlichen IP (Domain '{domain:s}' mit der IP '{ip:s}') zu erreichen. Möglicherweise ist dafür hairpinning oder eine falsch konfigurierte Firewall/Router deines Servers dafür verantwortlich.",
|
||||||
"certmanager_couldnt_fetch_intermediate_cert": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte die Teilzertifikate von Let's Encrypt zusammenzusetzen. Die Installation/Erneuerung des Zertifikats wurde abgebrochen — bitte versuche es später erneut.",
|
"certmanager_couldnt_fetch_intermediate_cert": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte die Teilzertifikate von Let's Encrypt zusammenzusetzen. Die Installation/Erneuerung des Zertifikats wurde abgebrochen — bitte versuche es später erneut.",
|
||||||
"domain_hostname_failed": "Erstellen des neuen Hostnamens fehlgeschlagen",
|
"domain_hostname_failed": "Erstellen des neuen Hostnamens fehlgeschlagen",
|
||||||
|
@ -402,5 +402,32 @@
|
||||||
"migration_description_0018_xtable_to_nftable": "Alte Netzwerkverkehrsregeln zum neuen nftable-System migrieren",
|
"migration_description_0018_xtable_to_nftable": "Alte Netzwerkverkehrsregeln zum neuen nftable-System migrieren",
|
||||||
"service_reload_failed": "Der Dienst '{service:s}' konnte nicht erneut geladen werden.\n\nKürzlich erstellte Logs des Dienstes: {logs:s}",
|
"service_reload_failed": "Der Dienst '{service:s}' konnte nicht erneut geladen werden.\n\nKürzlich erstellte Logs des Dienstes: {logs:s}",
|
||||||
"service_reloaded": "Der Dienst '{service:s}' wurde erneut geladen",
|
"service_reloaded": "Der Dienst '{service:s}' wurde erneut geladen",
|
||||||
"service_restart_failed": "Der Dienst '{service:s}' konnte nicht erneut gestartet werden.\n\nKürzlich erstellte Logs des Dienstes: {logs:s}"
|
"service_restart_failed": "Der Dienst '{service:s}' konnte nicht erneut gestartet werden.\n\nKürzlich erstellte Logs des Dienstes: {logs:s}",
|
||||||
|
"app_manifest_install_ask_password": "Wählen Sie ein Verwaltungspasswort für diese Applikation",
|
||||||
|
"app_manifest_install_ask_domain": "Wählen Sie die Domäne, auf welcher die Applikation installiert werden soll",
|
||||||
|
"log_letsencrypt_cert_renew": "Erneuern des Let's Encrypt-Zeritifikates von '{}'",
|
||||||
|
"log_selfsigned_cert_install": "Das selbstsignierte Zertifikat auf der Domäne '{}' installieren",
|
||||||
|
"log_letsencrypt_cert_install": "Das Let’s Encrypt auf der Domäne '{}' installieren",
|
||||||
|
"diagnosis_mail_fcrdns_nok_details": "Sie sollten zuerst versuchen, in Ihrer Internet-Router-Oberfläche oder in Ihrer Hosting-Anbieter-Oberfläche den Reverse-DNS-Eintrag mit <code>{ehlo_domain}</code>zu konfigurieren. (Gewisse Hosting-Anbieter können dafür möglicherweise verlangen, dass Sie dafür ein Support-Ticket erstellen).",
|
||||||
|
"diagnosis_mail_fcrdns_dns_missing": "Es wurde kein Reverse-DNS-Eintrag definiert für IPv{ipversion}. Einige E-Mails könnten möglicherweise zurückgewiesen oder als Spam markiert werden.",
|
||||||
|
"diagnosis_mail_fcrdns_ok": "Ihr Reverse-DNS-Eintrag ist korrekt konfiguriert!",
|
||||||
|
"diagnosis_mail_ehlo_could_not_diagnose_details": "Fehler: {error}",
|
||||||
|
"diagnosis_mail_ehlo_could_not_diagnose": "Konnte nicht überprüfen, ob der Postfix-Mail-Server von aussen per IPv{ipversion} erreichbar ist.",
|
||||||
|
"diagnosis_mail_ehlo_wrong_details": "Die vom Remote-Diagnose-Server per IPv{ipversion} empfangene EHLO weicht von der Domäne Ihres Servers ab. <br>Empfangene EHLO: <code>{wrong_ehlo}</code><br>Erwartet: <code>{right_ehlo}</code> <br> Die geläufigste Ursache für dieses Problem ist, dass der Port 25 <a href='https://yunohost.org/isp_box_config'> nicht korrekt auf Ihren Server weitergeleitet wird</a>. Sie können sich zusätzlich auch versichern, dass keine Firewall oder Reverse-Proxy interferiert.",
|
||||||
|
"diagnosis_mail_ehlo_bad_answer_details": "Das könnte daran liegen, dass anstelle Ihres Servers eine andere Maschine antwortet.",
|
||||||
|
"ask_user_domain": "Domäne, welche für die E-Mail-Adresse und den XMPP-Account des Benutzers verwendet werden soll",
|
||||||
|
"app_manifest_install_ask_is_public": "Soll diese Applikation für anonyme Benutzer sichtbar sein?",
|
||||||
|
"app_manifest_install_ask_admin": "Wählen Sie einen Administrator für diese Applikation",
|
||||||
|
"app_manifest_install_ask_path": "Wählen Sie den Pfad, in welchem die Applikation installiert werden soll",
|
||||||
|
"diagnosis_mail_blacklist_listed_by": "Ihre IP-Adresse oder Domäne <code>{item}</code> ist auf der Blacklist auf {blacklist_name}",
|
||||||
|
"diagnosis_mail_blacklist_ok": "Die IP-Adressen und die Domänen, welche von diesem Server verwendet werden, scheinen nicht auf einer Blacklist zu sein",
|
||||||
|
"diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Aktueller Reverse-DNS-Eintrag: <code>{rdns_domain}</code><br>Erwarteter Wert: <code>{ehlo_domain}</code>",
|
||||||
|
"diagnosis_mail_fcrdns_different_from_ehlo_domain": "Der Reverse-DNS-Eintrag für IPv{ipversion} ist nicht korrekt konfiguriert. Einige E-Mails könnten abgewiesen oder als Spam markiert werden.",
|
||||||
|
"diagnosis_mail_fcrdns_nok_alternatives_6": "Einige Provider werden es Ihnen nicht erlauben, Ihren Reverse-DNS-Eintrag zu konfigurieren (oder ihre Funktionalität könnte defekt sein ...). Falls Ihr Reverse-DNS-Eintrag für IPv4 korrekt konfiguiert ist, können Sie versuchen, die Verwendung von IPv6 für das Versenden von E-Mails auszuschalten, indem Sie den Befehl <cmd>yunohost settings set smtp.allow_ipv6 -v off</cmd> ausführen. Bemerkung: Die Folge dieser letzten Lösung ist, dass Sie mit Servern, welche ausschliesslich über IPv6 verfügen, keine E-Mails mehr versenden oder empfangen können.",
|
||||||
|
"diagnosis_mail_fcrdns_nok_alternatives_4": "Einige Anbieter werden es Ihnen nicht erlauben, dass Sie Ihren Reverse-DNS (oder deren Funktionalität ist defekt...) konfigurieren. Falls Sie deswegen auf Probleme stossen sollten, ziehen Sie folgende Lösungen in Betracht:<br> - Manche ISPs stellen als Alternative <a href='https://yunohost.org/#/smtp_relay'>die Benutzung eines Mail-Server-Relays</a> zur Verfügung, was jedoch mit sich zieht, dass das Relay Ihren E-Mail-Verkehr ausspionieren kann. <br> - Eine privatsphärenfreundlichere Alternative ist die Benutzung eines VPN *mit einer dedizierten öffentlichen IP* um Einschränkungen dieser Art zu umgehen. Schauen Sie hier nach <a href='https://yunohost.org/#/vpn_advantage'>https://yunohost.org/#/vpn_advantage</a><br>- Schliesslich ist es auch möglich <a href='https://yunohost.org/#/isp'>zu einem anderen Anbieter zu wechseln</a>",
|
||||||
|
"diagnosis_mail_queue_unavailable_details": "Fehler: {error}",
|
||||||
|
"diagnosis_mail_queue_unavailable": "Die Anzahl der anstehenden Nachrichten in der Warteschlange kann nicht abgefragt werden",
|
||||||
|
"diagnosis_mail_queue_ok": "{nb_pending} anstehende E-Mails in der Warteschlange",
|
||||||
|
"diagnosis_mail_blacklist_reason": "Der Grund für die Blacklist ist: {reason}",
|
||||||
|
"app_argument_password_no_default": "Fehler beim Verarbeiten des Passwort-Arguments '{name}': Passwort-Argument kann aus Sicherheitsgründen keinen Standardwert haben"
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"app_already_up_to_date": "{app:s} is already up-to-date",
|
"app_already_up_to_date": "{app:s} is already up-to-date",
|
||||||
"app_argument_choice_invalid": "Use one of these choices '{choices:s}' for the argument '{name:s}'",
|
"app_argument_choice_invalid": "Use one of these choices '{choices:s}' for the argument '{name:s}'",
|
||||||
"app_argument_invalid": "Pick a valid value for the argument '{name:s}': {error:s}",
|
"app_argument_invalid": "Pick a valid value for the argument '{name:s}': {error:s}",
|
||||||
|
"app_argument_password_no_default": "Error while parsing password argument '{name}': password argument can't have a default value for security reason",
|
||||||
"app_argument_required": "Argument '{name:s}' is required",
|
"app_argument_required": "Argument '{name:s}' is required",
|
||||||
"app_change_url_failed_nginx_reload": "Could not reload NGINX. Here is the output of 'nginx -t':\n{nginx_errors:s}",
|
"app_change_url_failed_nginx_reload": "Could not reload NGINX. Here is the output of 'nginx -t':\n{nginx_errors:s}",
|
||||||
"app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.",
|
"app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.",
|
||||||
|
@ -147,6 +148,8 @@
|
||||||
"diagnosis_basesystem_ynh_single_version": "{package} version: {version} ({repo})",
|
"diagnosis_basesystem_ynh_single_version": "{package} version: {version} ({repo})",
|
||||||
"diagnosis_basesystem_ynh_main_version": "Server is running YunoHost {main_version} ({repo})",
|
"diagnosis_basesystem_ynh_main_version": "Server is running YunoHost {main_version} ({repo})",
|
||||||
"diagnosis_basesystem_ynh_inconsistent_versions": "You are running inconsistent versions of the YunoHost packages... most probably because of a failed or partial upgrade.",
|
"diagnosis_basesystem_ynh_inconsistent_versions": "You are running inconsistent versions of the YunoHost packages... most probably because of a failed or partial upgrade.",
|
||||||
|
"diagnosis_package_installed_from_sury": "Some system packages should be downgraded",
|
||||||
|
"diagnosis_package_installed_from_sury_details": "Some packages were inadvertendly installed from a third-party repository called Sury. The Yunohost team improved the strategy that handle these packages, but it's expected that some setups that installed PHP7.3 apps while still on Stretch have some remaining inconsistencies. To fix this situation, you should try running the following command: <cmd>{cmd_to_fix}</cmd>",
|
||||||
"diagnosis_display_tip": "To see the issues found, you can go to the Diagnosis section of the webadmin, or run 'yunohost diagnosis show --issues' from the command-line.",
|
"diagnosis_display_tip": "To see the issues found, you can go to the Diagnosis section of the webadmin, or run 'yunohost diagnosis show --issues' from the command-line.",
|
||||||
"diagnosis_failed_for_category": "Diagnosis failed for category '{category}': {error}",
|
"diagnosis_failed_for_category": "Diagnosis failed for category '{category}': {error}",
|
||||||
"diagnosis_cache_still_valid": "(Cache still valid for {category} diagnosis. Won't re-diagnose it yet!)",
|
"diagnosis_cache_still_valid": "(Cache still valid for {category} diagnosis. Won't re-diagnose it yet!)",
|
||||||
|
@ -323,6 +326,10 @@
|
||||||
"global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discard it and save it in /etc/yunohost/settings-unknown.json",
|
"global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discard 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_setting_smtp_allow_ipv6": "Allow the use of IPv6 to receive and send mail",
|
"global_settings_setting_smtp_allow_ipv6": "Allow the use of IPv6 to receive and send mail",
|
||||||
|
"global_settings_setting_smtp_relay_host": "SMTP relay host to use in order to send mail instead of this yunohost instance. Useful if you are in one of this situation: your 25 port is blocked by your ISP or VPS provider, you have a residential IP listed on DUHL, you are not able to configure reverse DNS or this server is not directly exposed on the internet and you want use an other one to send mails.",
|
||||||
|
"global_settings_setting_smtp_relay_port": "SMTP relay port",
|
||||||
|
"global_settings_setting_smtp_relay_user": "SMTP relay user account",
|
||||||
|
"global_settings_setting_smtp_relay_password": "SMTP relay host password",
|
||||||
"global_settings_setting_backup_compress_tar_archives": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.",
|
"global_settings_setting_backup_compress_tar_archives": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.",
|
||||||
"global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it is 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 is not a type supported by the system.",
|
||||||
"good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).",
|
"good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).",
|
||||||
|
@ -474,7 +481,8 @@
|
||||||
"password_too_simple_4": "The password needs to be at least 12 characters long and contain a digit, upper, lower and special characters",
|
"password_too_simple_4": "The password needs to be at least 12 characters long and contain a digit, upper, lower and special characters",
|
||||||
"pattern_backup_archive_name": "Must be a valid filename with max 30 characters, alphanumeric and -_. characters only",
|
"pattern_backup_archive_name": "Must be a valid filename with max 30 characters, alphanumeric and -_. characters only",
|
||||||
"pattern_domain": "Must be a valid domain name (e.g. my-domain.org)",
|
"pattern_domain": "Must be a valid domain name (e.g. my-domain.org)",
|
||||||
"pattern_email": "Must be a valid e-mail address (e.g. someone@example.com)",
|
"pattern_email_forward": "Must be a valid e-mail address, '+' symbol accepted (e.g. someone+tag@example.com)",
|
||||||
|
"pattern_email": "Must be a valid e-mail address, without '+' symbol (e.g. someone@example.com)",
|
||||||
"pattern_firstname": "Must be a valid first name",
|
"pattern_firstname": "Must be a valid first name",
|
||||||
"pattern_lastname": "Must be a valid last name",
|
"pattern_lastname": "Must be a valid last name",
|
||||||
"pattern_mailbox_quota": "Must be a size with b/k/M/G/T suffix or 0 to not have a quota",
|
"pattern_mailbox_quota": "Must be a size with b/k/M/G/T suffix or 0 to not have a quota",
|
||||||
|
@ -617,7 +625,6 @@
|
||||||
"user_unknown": "Unknown user: {user:s}",
|
"user_unknown": "Unknown user: {user:s}",
|
||||||
"user_update_failed": "Could not update user {user}: {error}",
|
"user_update_failed": "Could not update user {user}: {error}",
|
||||||
"user_updated": "User info changed",
|
"user_updated": "User info changed",
|
||||||
"users_available": "Available users:",
|
|
||||||
"yunohost_already_installed": "YunoHost is already installed",
|
"yunohost_already_installed": "YunoHost is already installed",
|
||||||
"yunohost_ca_creation_failed": "Could not create certificate authority",
|
"yunohost_ca_creation_failed": "Could not create certificate authority",
|
||||||
"yunohost_ca_creation_success": "Local certification authority created.",
|
"yunohost_ca_creation_success": "Local certification authority created.",
|
||||||
|
|
|
@ -107,7 +107,7 @@
|
||||||
"port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version:s}",
|
"port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version:s}",
|
||||||
"port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version:s}",
|
"port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version:s}",
|
||||||
"restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app:s}'",
|
"restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app:s}'",
|
||||||
"restore_app_failed": "Impossible de restaurer l’application '{app:s}'",
|
"restore_app_failed": "Impossible de restaurer '{app:s}'",
|
||||||
"restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration",
|
"restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration",
|
||||||
"restore_complete": "Restauré",
|
"restore_complete": "Restauré",
|
||||||
"restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]",
|
"restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]",
|
||||||
|
@ -136,13 +136,13 @@
|
||||||
"ssowat_conf_updated": "La configuration de SSOwat mise à jour",
|
"ssowat_conf_updated": "La configuration de SSOwat mise à jour",
|
||||||
"system_upgraded": "Système mis à jour",
|
"system_upgraded": "Système mis à jour",
|
||||||
"system_username_exists": "Ce nom d’utilisateur existe déjà dans les utilisateurs système",
|
"system_username_exists": "Ce nom d’utilisateur existe déjà dans les utilisateurs système",
|
||||||
"unbackup_app": "L’application '{app:s}' ne sera pas sauvegardée",
|
"unbackup_app": "'{app:s}' ne sera pas sauvegardée",
|
||||||
"unexpected_error": "Une erreur inattendue est survenue : {error}",
|
"unexpected_error": "Une erreur inattendue est survenue : {error}",
|
||||||
"unlimit": "Pas de quota",
|
"unlimit": "Pas de quota",
|
||||||
"unrestore_app": "L’application '{app:s}' ne sera pas restaurée",
|
"unrestore_app": "'{app:s}' ne sera pas restaurée",
|
||||||
"updating_apt_cache": "Récupération des mises à jour disponibles pour les paquets du système…",
|
"updating_apt_cache": "Récupération des mises à jour disponibles pour les paquets du système...",
|
||||||
"upgrade_complete": "Mise à jour terminée",
|
"upgrade_complete": "Mise à jour terminée",
|
||||||
"upgrading_packages": "Mise à jour des paquets en cours…",
|
"upgrading_packages": "Mise à jour des paquets en cours...",
|
||||||
"upnp_dev_not_found": "Aucun périphérique compatible UPnP n’a été trouvé",
|
"upnp_dev_not_found": "Aucun périphérique compatible UPnP n’a été trouvé",
|
||||||
"upnp_disabled": "UPnP désactivé",
|
"upnp_disabled": "UPnP désactivé",
|
||||||
"upnp_enabled": "UPnP activé",
|
"upnp_enabled": "UPnP activé",
|
||||||
|
@ -158,7 +158,7 @@
|
||||||
"yunohost_already_installed": "YunoHost est déjà installé",
|
"yunohost_already_installed": "YunoHost est déjà installé",
|
||||||
"yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification",
|
"yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification",
|
||||||
"yunohost_configured": "YunoHost est maintenant configuré",
|
"yunohost_configured": "YunoHost est maintenant configuré",
|
||||||
"yunohost_installing": "L’installation de YunoHost est en cours…",
|
"yunohost_installing": "L’installation de YunoHost est en cours...",
|
||||||
"yunohost_not_installed": "YunoHost n’est pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'",
|
"yunohost_not_installed": "YunoHost n’est pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'",
|
||||||
"certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner cela)",
|
"certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner cela)",
|
||||||
"certmanager_domain_unknown": "Domaine {domain:s} inconnu",
|
"certmanager_domain_unknown": "Domaine {domain:s} inconnu",
|
||||||
|
@ -241,10 +241,10 @@
|
||||||
"backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.",
|
"backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.",
|
||||||
"domain_dns_conf_is_just_a_recommendation": "Cette page montre la configuration *recommandée*. Elle ne configure *pas* le DNS pour vous. Il est de votre responsabilité que de configurer votre zone DNS chez votre fournisseur/registrar DNS avec cette recommandation.",
|
"domain_dns_conf_is_just_a_recommendation": "Cette page montre la configuration *recommandée*. Elle ne configure *pas* le DNS pour vous. Il est de votre responsabilité que de configurer votre zone DNS chez votre fournisseur/registrar DNS avec cette recommandation.",
|
||||||
"migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migration via le chemin '%s'",
|
"migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migration via le chemin '%s'",
|
||||||
"migrations_loading_migration": "Chargement de la migration {id} …",
|
"migrations_loading_migration": "Chargement de la migration {id}...",
|
||||||
"migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation",
|
"migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation",
|
||||||
"migrations_no_migrations_to_run": "Aucune migration à lancer",
|
"migrations_no_migrations_to_run": "Aucune migration à lancer",
|
||||||
"migrations_skip_migration": "Ignorer et passer la migration {id}…",
|
"migrations_skip_migration": "Ignorer et passer la migration {id}...",
|
||||||
"server_shutdown": "Le serveur va s’éteindre",
|
"server_shutdown": "Le serveur va s’éteindre",
|
||||||
"server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers:s}]",
|
"server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers:s}]",
|
||||||
"server_reboot": "Le serveur va redémarrer",
|
"server_reboot": "Le serveur va redémarrer",
|
||||||
|
@ -415,7 +415,7 @@
|
||||||
"regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'",
|
"regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'",
|
||||||
"regenconf_dry_pending_applying": "Vérification de la configuration en attente qui aurait été appliquée pour la catégorie '{category}'…",
|
"regenconf_dry_pending_applying": "Vérification de la configuration en attente qui aurait été appliquée pour la catégorie '{category}'…",
|
||||||
"regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'",
|
"regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'",
|
||||||
"regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}' …",
|
"regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}'...",
|
||||||
"service_regen_conf_is_deprecated": "'yunohost service regen-conf' est obsolète ! Veuillez plutôt utiliser 'yunohost tools regen-conf' à la place.",
|
"service_regen_conf_is_deprecated": "'yunohost service regen-conf' est obsolète ! Veuillez plutôt utiliser 'yunohost tools regen-conf' à la place.",
|
||||||
"tools_upgrade_at_least_one": "Veuillez spécifier '--apps' ou '--system'",
|
"tools_upgrade_at_least_one": "Veuillez spécifier '--apps' ou '--system'",
|
||||||
"tools_upgrade_cant_both": "Impossible de mettre à niveau le système et les applications en même temps",
|
"tools_upgrade_cant_both": "Impossible de mettre à niveau le système et les applications en même temps",
|
||||||
|
@ -451,21 +451,21 @@
|
||||||
"migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.",
|
"migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.",
|
||||||
"migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées : {ids}",
|
"migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées : {ids}",
|
||||||
"migration_0011_can_not_backup_before_migration": "La sauvegarde du système n’a pas pu être terminée avant l’échec de la migration. Erreur : {error:s}",
|
"migration_0011_can_not_backup_before_migration": "La sauvegarde du système n’a pas pu être terminée avant l’échec de la migration. Erreur : {error:s}",
|
||||||
"migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP…",
|
"migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP...",
|
||||||
"migration_0011_migration_failed_trying_to_rollback": "La migration a échoué… Tentative de restauration du système.",
|
"migration_0011_migration_failed_trying_to_rollback": "La migration a échoué… Tentative de restauration du système.",
|
||||||
"migration_0011_rollback_success": "Système restauré.",
|
"migration_0011_rollback_success": "Système restauré.",
|
||||||
"migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP…",
|
"migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP...",
|
||||||
"migration_0011_backup_before_migration": "Création d’une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.",
|
"migration_0011_backup_before_migration": "Création d’une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.",
|
||||||
"permission_not_found": "Autorisation '{permission:s}' introuvable",
|
"permission_not_found": "Autorisation '{permission:s}' introuvable",
|
||||||
"permission_update_failed": "Impossible de mettre à jour l’autorisation '{permission}' : {error}",
|
"permission_update_failed": "Impossible de mettre à jour l’autorisation '{permission}' : {error}",
|
||||||
"permission_updated": "Permission '{permission:s}' mise à jour",
|
"permission_updated": "Permission '{permission:s}' mise à jour",
|
||||||
"permission_update_nothing_to_do": "Aucune autorisation pour mettre à jour",
|
"permission_update_nothing_to_do": "Aucune autorisation pour mettre à jour",
|
||||||
"dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur DynDNS {provider} : votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.",
|
"dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur DynDNS {provider} : votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.",
|
||||||
"migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP…",
|
"migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP...",
|
||||||
"migrations_already_ran": "Ces migrations sont déjà effectuées : {ids}",
|
"migrations_already_ran": "Ces migrations sont déjà effectuées : {ids}",
|
||||||
"migrations_dependencies_not_satisfied": "Exécutez ces migrations : '{dependencies_id}', avant migration {id}.",
|
"migrations_dependencies_not_satisfied": "Exécutez ces migrations : '{dependencies_id}', avant migration {id}.",
|
||||||
"migrations_failed_to_load_migration": "Impossible de charger la migration {id} : {error}",
|
"migrations_failed_to_load_migration": "Impossible de charger la migration {id} : {error}",
|
||||||
"migrations_running_forward": "Exécution de la migration {id}…",
|
"migrations_running_forward": "Exécution de la migration {id}...",
|
||||||
"migrations_success_forward": "Migration {id} terminée",
|
"migrations_success_forward": "Migration {id} terminée",
|
||||||
"operation_interrupted": "L’opération a été interrompue manuellement ?",
|
"operation_interrupted": "L’opération a été interrompue manuellement ?",
|
||||||
"permission_already_exist": "L’autorisation '{permission}' existe déjà",
|
"permission_already_exist": "L’autorisation '{permission}' existe déjà",
|
||||||
|
@ -638,7 +638,7 @@
|
||||||
"diagnosis_http_partially_unreachable": "Le domaine {domain} semble inaccessible en HTTP depuis l’extérieur du réseau local en IPv{failed}, bien qu’il fonctionne en IPv{passed}.",
|
"diagnosis_http_partially_unreachable": "Le domaine {domain} semble inaccessible en HTTP depuis l’extérieur du réseau local en IPv{failed}, bien qu’il fonctionne en IPv{passed}.",
|
||||||
"diagnosis_http_nginx_conf_not_up_to_date": "La configuration Nginx de ce domaine semble avoir été modifiée manuellement et empêche YunoHost de diagnostiquer si elle est accessible en HTTP.",
|
"diagnosis_http_nginx_conf_not_up_to_date": "La configuration Nginx de ce domaine semble avoir été modifiée manuellement et empêche YunoHost de diagnostiquer si elle est accessible en HTTP.",
|
||||||
"diagnosis_http_nginx_conf_not_up_to_date_details": "Pour corriger la situation, inspectez la différence avec la ligne de commande en utilisant les outils <cmd>yunohost tools regen-conf nginx --dry-run --with-diff</cmd> et si vous êtes d’accord, appliquez les modifications avec <cmd>yunohost tools regen-conf nginx --force</cmd>.",
|
"diagnosis_http_nginx_conf_not_up_to_date_details": "Pour corriger la situation, inspectez la différence avec la ligne de commande en utilisant les outils <cmd>yunohost tools regen-conf nginx --dry-run --with-diff</cmd> et si vous êtes d’accord, appliquez les modifications avec <cmd>yunohost tools regen-conf nginx --force</cmd>.",
|
||||||
"backup_archive_cant_retrieve_info_json": "Impossible d'avoir des informations sur l'archive '{archive}' ... Le fichier info.json ne peut pas être trouvé (ou n'est pas un fichier json valide).",
|
"backup_archive_cant_retrieve_info_json": "Impossible d'avoir des informations sur l'archive '{archive}'... Le fichier info.json ne peut pas être trouvé (ou n'est pas un fichier json valide).",
|
||||||
"backup_archive_corrupted": "Il semble que l'archive de la sauvegarde '{archive}' est corrompue : {error}",
|
"backup_archive_corrupted": "Il semble que l'archive de la sauvegarde '{archive}' est corrompue : {error}",
|
||||||
"diagnosis_ip_no_ipv6_tip": "L'utilisation de IPv6 n'est pas obligatoire pour le fonctionnement de votre serveur, mais cela contribue à la santé d'Internet dans son ensemble. IPv6 généralement configuré automatiquement par votre système ou votre FAI s'il est disponible. Autrement, vous devrez prendre quelque minutes pour le configurer manuellement à l'aide de cette documentation: <a href='https://yunohost.org/#/ipv6'>https://yunohost.org/#/ipv6</a>. Si vous ne pouvez pas activer IPv6 ou si c'est trop technique pour vous, vous pouvez aussi ignorer cet avertissement sans que cela pose problème.",
|
"diagnosis_ip_no_ipv6_tip": "L'utilisation de IPv6 n'est pas obligatoire pour le fonctionnement de votre serveur, mais cela contribue à la santé d'Internet dans son ensemble. IPv6 généralement configuré automatiquement par votre système ou votre FAI s'il est disponible. Autrement, vous devrez prendre quelque minutes pour le configurer manuellement à l'aide de cette documentation: <a href='https://yunohost.org/#/ipv6'>https://yunohost.org/#/ipv6</a>. Si vous ne pouvez pas activer IPv6 ou si c'est trop technique pour vous, vous pouvez aussi ignorer cet avertissement sans que cela pose problème.",
|
||||||
"diagnosis_domain_expiration_not_found": "Impossible de vérifier la date d'expiration de certains domaines",
|
"diagnosis_domain_expiration_not_found": "Impossible de vérifier la date d'expiration de certains domaines",
|
||||||
|
@ -652,21 +652,43 @@
|
||||||
"diagnosis_swap_tip": "Merci d'être prudent et conscient que si vous hébergez une partition SWAP sur une carte SD ou un disque SSD, cela risque de réduire drastiquement l’espérance de vie du périphérique.",
|
"diagnosis_swap_tip": "Merci d'être prudent et conscient que si vous hébergez une partition SWAP sur une carte SD ou un disque SSD, cela risque de réduire drastiquement l’espérance de vie du périphérique.",
|
||||||
"restore_already_installed_apps": "Les applications suivantes ne peuvent pas être restaurées car elles sont déjà installées : {apps}",
|
"restore_already_installed_apps": "Les applications suivantes ne peuvent pas être restaurées car elles sont déjà installées : {apps}",
|
||||||
"regenconf_need_to_explicitly_specify_ssh": "La configuration de ssh a été modifiée manuellement. Vous devez explicitement indiquer la mention --force à \"ssh\" pour appliquer les changements.",
|
"regenconf_need_to_explicitly_specify_ssh": "La configuration de ssh a été modifiée manuellement. Vous devez explicitement indiquer la mention --force à \"ssh\" pour appliquer les changements.",
|
||||||
"migration_0015_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus utiles …",
|
"migration_0015_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus utiles...",
|
||||||
"migration_0015_specific_upgrade": "Commencement de la mise à jour des paquets du système qui doivent être mis à jour séparément …",
|
"migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent être mis à jour séparément...",
|
||||||
"migration_0015_modified_files": "Veuillez noter que les fichiers suivants ont été modifiés manuellement et pourraient être écrasés à la suite de la mise à niveau : {manually_modified_files}",
|
"migration_0015_modified_files": "Veuillez noter que les fichiers suivants ont été modifiés manuellement et pourraient être écrasés à la suite de la mise à niveau : {manually_modified_files}",
|
||||||
"migration_0015_problematic_apps_warning": "Veuillez noter que des applications qui peuvent poser problèmes ont été détectées. Il semble qu'elles n'aient pas été installées à partir du catalogue d'applications YunoHost, ou bien qu'elles ne soient pas signalées comme \"fonctionnelles\". Par conséquent, il n'est pas possible de garantir que les applications suivantes fonctionneront encore après la mise à niveau : {problematic_apps}",
|
"migration_0015_problematic_apps_warning": "Veuillez noter que des applications qui peuvent poser problèmes ont été détectées. Il semble qu'elles n'aient pas été installées à partir du catalogue d'applications YunoHost, ou bien qu'elles ne soient pas signalées comme \"fonctionnelles\". Par conséquent, il n'est pas possible de garantir que les applications suivantes fonctionneront encore après la mise à niveau : {problematic_apps}",
|
||||||
"migration_0015_general_warning": "Veuillez noter que cette migration est une opération délicate. L'équipe YunoHost a fait de son mieux pour la revérifier et la tester, mais la migration pourrait quand même casser des éléments du système ou de ses applications.\n\nIl est donc recommandé :\n…- de faire une sauvegarde de toute donnée ou application critique. Plus d'informations ici https://yunohost.org/backup ;\n…- d'être patient après le lancement de la migration. Selon votre connexion internet et votre matériel, la mise à niveau peut prendre jusqu'à quelques heures.",
|
"migration_0015_general_warning": "Veuillez noter que cette migration est une opération délicate. L'équipe YunoHost a fait de son mieux pour la revérifier et la tester, mais la migration pourrait quand même casser des éléments du système ou de ses applications.\n\nIl est donc recommandé :\n…- de faire une sauvegarde de toute donnée ou application critique. Plus d'informations ici https://yunohost.org/backup ;\n…- d'être patient après le lancement de la migration. Selon votre connexion internet et votre matériel, la mise à niveau peut prendre jusqu'à quelques heures.",
|
||||||
"migration_0015_system_not_fully_up_to_date": "Votre système n'est pas entièrement à jour. Veuillez effectuer une mise à jour normale avant de lancer la migration vers Buster.",
|
"migration_0015_system_not_fully_up_to_date": "Votre système n'est pas entièrement à jour. Veuillez effectuer une mise à jour normale avant de lancer la migration vers Buster.",
|
||||||
"migration_0015_not_enough_free_space": "L'espace libre est très faible dans /var/ ! Vous devriez avoir au moins 1 Go de libre pour effectuer cette migration.",
|
"migration_0015_not_enough_free_space": "L'espace libre est très faible dans /var/ ! Vous devriez avoir au moins 1 Go de libre pour effectuer cette migration.",
|
||||||
"migration_0015_not_stretch": "La distribution Debian actuelle n'est pas Stretch !",
|
"migration_0015_not_stretch": "La distribution Debian actuelle n'est pas Stretch !",
|
||||||
"migration_0015_yunohost_upgrade": "Démarrage de la mise à jour de YunoHost …",
|
"migration_0015_yunohost_upgrade": "Démarrage de la mise à jour de YunoHost...",
|
||||||
"migration_0015_still_on_stretch_after_main_upgrade": "Quelque chose s'est mal passé lors de la mise à niveau, le système semble toujours être sous Debian Stretch",
|
"migration_0015_still_on_stretch_after_main_upgrade": "Quelque chose s'est mal passé lors de la mise à niveau, le système semble toujours être sous Debian Stretch",
|
||||||
"migration_0015_main_upgrade": "Démarrage de la mise à niveau générale …",
|
"migration_0015_main_upgrade": "Démarrage de la mise à niveau générale...",
|
||||||
"migration_0015_patching_sources_list": "Mise à jour du fichier sources.lists …",
|
"migration_0015_patching_sources_list": "Mise à jour du fichier sources.lists...",
|
||||||
"migration_0015_start": "Démarrage de la migration vers Buster",
|
"migration_0015_start": "Démarrage de la migration vers Buster",
|
||||||
"migration_description_0015_migrate_to_buster": "Mise à niveau du système vers Debian Buster et YunoHost 4.x",
|
"migration_description_0015_migrate_to_buster": "Mise à niveau du système vers Debian Buster et YunoHost 4.x",
|
||||||
"diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par Yunohost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant <cmd>yunohost dyndns update --force</cmd>.",
|
"diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par Yunohost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant <cmd>yunohost dyndns update --force</cmd>.",
|
||||||
"app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.",
|
"app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.",
|
||||||
"migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de nginx : {certs}"
|
"migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de NGINX : {certs}",
|
||||||
|
"global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu des archives non-compressées lors de la création des backups. N.B. : activer cette option permet d'obtenir des sauvegardes plus légères, mais leur création sera significativement plus longue et plus gourmande en CPU.",
|
||||||
|
"migration_description_0018_xtable_to_nftable": "Migrer les anciennes règles de trafic réseau vers le nouveau système basé sur nftables",
|
||||||
|
"service_description_php7.3-fpm": "Exécute les applications écrites en PHP avec NGINX",
|
||||||
|
"migration_0018_failed_to_reset_legacy_rules": "La réinitialisation des règles iptable legacy a échoué :",
|
||||||
|
"migration_0018_failed_to_migrate_iptables_rules": "La migration des anciennes règles iptables vers nftables a échoué : {error}",
|
||||||
|
"migration_0017_not_enough_space": "Laissez suffisamment d'espace disponible dans {path} avant de lancer la migration.",
|
||||||
|
"migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 est installé mais pas posgreSQL 11 ? Il s'est sans doute passé quelque chose d'étrange sur votre système :(...",
|
||||||
|
"migration_0017_postgresql_96_not_installed": "PostgreSQL n'a pas été installé sur votre système. Aucune opération à effectuer.",
|
||||||
|
"migration_description_0017_postgresql_9p6_to_11": "Migrer les bases de données de PostgreSQL 9.6 vers 11",
|
||||||
|
"migration_description_0016_php70_to_php73_pools": "Migrer les configurations php7.0 vers php7.3",
|
||||||
|
"diagnosis_processes_killed_by_oom_reaper": "Certains processus ont été arrêtés récemment par le système car il manquait de mémoire. Cela apparaît généralement quand le système manque de mémoire ou qu'un processus consomme trop de mémoire. Liste des processus tués :\n{kills_summary}",
|
||||||
|
"ask_user_domain": "Domaine à utiliser pour l'adresse email de l'utilisateur et le compte XMPP",
|
||||||
|
"app_manifest_install_ask_is_public": "Cette application devrait-elle être visible par les visiteurs anonymes ?",
|
||||||
|
"app_manifest_install_ask_admin": "Choisissez un administrateur pour cette application",
|
||||||
|
"app_manifest_install_ask_password": "Choisissez un mot de passe administrateur pour cette application",
|
||||||
|
"app_manifest_install_ask_path": "Choisissez le chemin sur lequel vous souhaitez installer cette application",
|
||||||
|
"app_manifest_install_ask_domain": "Choisissez le domaine sur lequel vous souhaitez installer cette application",
|
||||||
|
"global_settings_setting_smtp_relay_user": "Relais de compte utilisateur SMTP",
|
||||||
|
"global_settings_setting_smtp_relay_port": "Port relais SMTP",
|
||||||
|
"global_settings_setting_smtp_relay_host": "Relais SMTP à utiliser pour envoyer du courrier à la place de cette instance YunoHost. Utile si vous êtes dans l'une de ces situations : votre port 25 est bloqué par votre FAI ou votre fournisseur VPS, vous avez une IP résidentielle répertoriée sur DUHL, vous ne pouvez pas configurer de DNS inversé ou ce serveur n'est pas directement exposé sur Internet et vous voulez en utiliser un autre pour envoyer des mails.",
|
||||||
|
"diagnosis_package_installed_from_sury_details": "Certains paquets ont été installés par inadvertance à partir d'un dépôt tiers appelé Sury. L'équipe YunoHost a amélioré la stratégie de gestion de ces paquets, mais on s'attend à ce que certaines configurations qui ont installé des applications PHP7.3 tout en étant toujours sur Stretch présentent des incohérences. Pour résoudre cette situation, vous devez essayer d'exécuter la commande suivante : <cmd> {cmd_to_fix} </cmd>",
|
||||||
|
"app_argument_password_no_default": "Erreur lors de l'analyse de l'argument de mot de passe '{name}' : l'argument de mot de passe ne peut pas avoir de valeur par défaut pour des raisons de sécurité"
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,7 +190,7 @@ def init_logging(interface="cli",
|
||||||
'loggers': {
|
'loggers': {
|
||||||
'yunohost': {
|
'yunohost': {
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'handlers': ['file', 'api'] + ['console'] if debug else [],
|
'handlers': ['file', 'api'] + (['console'] if debug else []),
|
||||||
'propagate': False,
|
'propagate': False,
|
||||||
},
|
},
|
||||||
'moulinette': {
|
'moulinette': {
|
||||||
|
@ -201,6 +201,6 @@ def init_logging(interface="cli",
|
||||||
},
|
},
|
||||||
'root': {
|
'root': {
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'handlers': ['file'] + ['console'] if debug else [],
|
'handlers': ['file'] + (['console'] if debug else []),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -39,6 +39,7 @@ from collections import OrderedDict
|
||||||
from moulinette import msignals, m18n, msettings
|
from moulinette import msignals, m18n, msettings
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
from moulinette.utils.network import download_json
|
from moulinette.utils.network import download_json
|
||||||
|
from moulinette.utils.process import run_commands
|
||||||
from moulinette.utils.filesystem import read_file, read_json, read_toml, read_yaml, write_to_file, write_to_json, write_to_yaml, chmod, chown, mkdir
|
from moulinette.utils.filesystem import read_file, read_json, read_toml, read_yaml, write_to_file, write_to_json, write_to_yaml, chmod, chown, mkdir
|
||||||
|
|
||||||
from yunohost.service import service_status, _run_service_command
|
from yunohost.service import service_status, _run_service_command
|
||||||
|
@ -59,12 +60,6 @@ APPS_CATALOG_CRON_PATH = "/etc/cron.daily/yunohost-fetch-apps-catalog"
|
||||||
APPS_CATALOG_API_VERSION = 2
|
APPS_CATALOG_API_VERSION = 2
|
||||||
APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default"
|
APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default"
|
||||||
|
|
||||||
re_github_repo = re.compile(
|
|
||||||
r'^(http[s]?://|git@)github.com[/:]'
|
|
||||||
'(?P<owner>[\w\-_]+)/(?P<repo>[\w\-_]+)(.git)?'
|
|
||||||
'(/tree/(?P<tree>.+))?'
|
|
||||||
)
|
|
||||||
|
|
||||||
re_app_instance_name = re.compile(
|
re_app_instance_name = re.compile(
|
||||||
r'^(?P<appid>[\w-]+?)(__(?P<appinstancenb>[1-9][0-9]*))?$'
|
r'^(?P<appid>[\w-]+?)(__(?P<appinstancenb>[1-9][0-9]*))?$'
|
||||||
)
|
)
|
||||||
|
@ -2171,61 +2166,13 @@ def _fetch_app_from_git(app):
|
||||||
|
|
||||||
logger.debug(m18n.n('downloading'))
|
logger.debug(m18n.n('downloading'))
|
||||||
|
|
||||||
|
# Extract URL, branch and revision to download
|
||||||
if ('@' in app) or ('http://' in app) or ('https://' in app):
|
if ('@' in app) or ('http://' in app) or ('https://' in app):
|
||||||
url = app
|
url = app
|
||||||
branch = 'master'
|
branch = 'master'
|
||||||
github_repo = re_github_repo.match(app)
|
if "/tree/" in url:
|
||||||
if github_repo:
|
url, branch = url.split("/tree/", 1)
|
||||||
if github_repo.group('tree'):
|
revision = 'HEAD'
|
||||||
branch = github_repo.group('tree')
|
|
||||||
url = "https://github.com/{owner}/{repo}".format(
|
|
||||||
owner=github_repo.group('owner'),
|
|
||||||
repo=github_repo.group('repo'),
|
|
||||||
)
|
|
||||||
tarball_url = "{url}/archive/{tree}.zip".format(
|
|
||||||
url=url, tree=branch
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
subprocess.check_call([
|
|
||||||
'wget', '-qO', app_tmp_archive, tarball_url])
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
logger.exception('unable to download %s', tarball_url)
|
|
||||||
raise YunohostError('app_sources_fetch_failed')
|
|
||||||
else:
|
|
||||||
manifest, extracted_app_folder = _extract_app_from_file(
|
|
||||||
app_tmp_archive, remove=True)
|
|
||||||
else:
|
|
||||||
tree_index = url.rfind('/tree/')
|
|
||||||
if tree_index > 0:
|
|
||||||
url = url[:tree_index]
|
|
||||||
branch = app[tree_index + 6:]
|
|
||||||
try:
|
|
||||||
# We use currently git 2.1 so we can't use --shallow-submodules
|
|
||||||
# option. When git will be in 2.9 (with the new debian version)
|
|
||||||
# we will be able to use it. Without this option all the history
|
|
||||||
# of the submodules repo is downloaded.
|
|
||||||
subprocess.check_call([
|
|
||||||
'git', 'clone', '-b', branch, '--single-branch', '--recursive', '--depth=1', url,
|
|
||||||
extracted_app_folder])
|
|
||||||
subprocess.check_call([
|
|
||||||
'git', 'reset', '--hard', branch
|
|
||||||
], cwd=extracted_app_folder)
|
|
||||||
manifest = _get_manifest_of_app(extracted_app_folder)
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
raise YunohostError('app_sources_fetch_failed')
|
|
||||||
except ValueError as e:
|
|
||||||
raise YunohostError('app_manifest_invalid', error=e)
|
|
||||||
else:
|
|
||||||
logger.debug(m18n.n('done'))
|
|
||||||
|
|
||||||
# Store remote repository info into the returned manifest
|
|
||||||
manifest['remote'] = {'type': 'git', 'url': url, 'branch': branch}
|
|
||||||
try:
|
|
||||||
revision = _get_git_last_commit_hash(url, branch)
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug("cannot get last commit hash because: %s ", e)
|
|
||||||
else:
|
|
||||||
manifest['remote']['revision'] = revision
|
|
||||||
else:
|
else:
|
||||||
app_dict = _load_apps_catalog()["apps"]
|
app_dict = _load_apps_catalog()["apps"]
|
||||||
|
|
||||||
|
@ -2237,47 +2184,39 @@ def _fetch_app_from_git(app):
|
||||||
raise YunohostError('app_unsupported_remote_type')
|
raise YunohostError('app_unsupported_remote_type')
|
||||||
|
|
||||||
app_info = app_dict[app_id]
|
app_info = app_dict[app_id]
|
||||||
app_info['manifest']['lastUpdate'] = app_info['lastUpdate']
|
|
||||||
manifest = app_info['manifest']
|
|
||||||
url = app_info['git']['url']
|
url = app_info['git']['url']
|
||||||
|
branch = app_info['git']['branch']
|
||||||
|
revision = str(app_info['git']['revision'])
|
||||||
|
|
||||||
if 'github.com' in url:
|
# Download only this commit
|
||||||
tarball_url = "{url}/archive/{tree}.zip".format(
|
try:
|
||||||
url=url, tree=app_info['git']['revision']
|
# We don't use git clone because, git clone can't download
|
||||||
)
|
# a specific revision only
|
||||||
try:
|
run_commands([['git', 'init', extracted_app_folder]], shell=False)
|
||||||
subprocess.check_call([
|
run_commands([
|
||||||
'wget', '-qO', app_tmp_archive, tarball_url])
|
['git', 'remote', 'add', 'origin', url],
|
||||||
except subprocess.CalledProcessError:
|
['git', 'fetch', '--depth=1', 'origin',
|
||||||
logger.exception('unable to download %s', tarball_url)
|
branch if revision == 'HEAD' else revision],
|
||||||
raise YunohostError('app_sources_fetch_failed')
|
['git', 'reset', '--hard', 'FETCH_HEAD']
|
||||||
else:
|
], cwd=extracted_app_folder, shell=False)
|
||||||
manifest, extracted_app_folder = _extract_app_from_file(
|
manifest = _get_manifest_of_app(extracted_app_folder)
|
||||||
app_tmp_archive, remove=True)
|
except subprocess.CalledProcessError:
|
||||||
else:
|
raise YunohostError('app_sources_fetch_failed')
|
||||||
try:
|
except ValueError as e:
|
||||||
subprocess.check_call([
|
raise YunohostError('app_manifest_invalid', error=e)
|
||||||
'git', 'clone', app_info['git']['url'],
|
else:
|
||||||
'-b', app_info['git']['branch'], extracted_app_folder])
|
logger.debug(m18n.n('done'))
|
||||||
subprocess.check_call([
|
|
||||||
'git', 'reset', '--hard',
|
|
||||||
str(app_info['git']['revision'])
|
|
||||||
], cwd=extracted_app_folder)
|
|
||||||
manifest = _get_manifest_of_app(extracted_app_folder)
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
raise YunohostError('app_sources_fetch_failed')
|
|
||||||
except ValueError as e:
|
|
||||||
raise YunohostError('app_manifest_invalid', error=e)
|
|
||||||
else:
|
|
||||||
logger.debug(m18n.n('done'))
|
|
||||||
|
|
||||||
# Store remote repository info into the returned manifest
|
# Store remote repository info into the returned manifest
|
||||||
manifest['remote'] = {
|
manifest['remote'] = {'type': 'git', 'url': url, 'branch': branch}
|
||||||
'type': 'git',
|
if revision == 'HEAD':
|
||||||
'url': url,
|
try:
|
||||||
'branch': app_info['git']['branch'],
|
manifest['remote']['revision'] = _get_git_last_commit_hash(url, branch)
|
||||||
'revision': app_info['git']['revision'],
|
except Exception as e:
|
||||||
}
|
logger.debug("cannot get last commit hash because: %s ", e)
|
||||||
|
else:
|
||||||
|
manifest['remote']['revision'] = revision
|
||||||
|
manifest['lastUpdate'] = app_info['lastUpdate']
|
||||||
|
|
||||||
return manifest, extracted_app_folder
|
return manifest, extracted_app_folder
|
||||||
|
|
||||||
|
@ -2442,6 +2381,225 @@ def _parse_args_for_action(action, args={}):
|
||||||
return _parse_args_in_yunohost_format(args, action_args)
|
return _parse_args_in_yunohost_format(args, action_args)
|
||||||
|
|
||||||
|
|
||||||
|
class Question:
|
||||||
|
"empty class to store questions information"
|
||||||
|
|
||||||
|
|
||||||
|
class YunoHostArgumentFormatParser(object):
|
||||||
|
hide_user_input_in_prompt = False
|
||||||
|
|
||||||
|
def parse_question(self, question, user_answers):
|
||||||
|
parsed_question = Question()
|
||||||
|
|
||||||
|
parsed_question.name = question['name']
|
||||||
|
parsed_question.default = question.get('default', None)
|
||||||
|
parsed_question.choices = question.get('choices', [])
|
||||||
|
parsed_question.optional = question.get('optional', False)
|
||||||
|
parsed_question.ask = question.get('ask')
|
||||||
|
parsed_question.value = user_answers.get(parsed_question.name)
|
||||||
|
|
||||||
|
if parsed_question.ask is None:
|
||||||
|
parsed_question.ask = "Enter value for '%s':" % parsed_question.name
|
||||||
|
|
||||||
|
return parsed_question
|
||||||
|
|
||||||
|
def parse(self, question, user_answers):
|
||||||
|
question = self.parse_question(question, user_answers)
|
||||||
|
|
||||||
|
if question.value is None:
|
||||||
|
text_for_user_input_in_cli = self._format_text_for_user_input_in_cli(question)
|
||||||
|
|
||||||
|
try:
|
||||||
|
question.value = msignals.prompt(text_for_user_input_in_cli, self.hide_user_input_in_prompt)
|
||||||
|
except NotImplementedError:
|
||||||
|
question.value = None
|
||||||
|
|
||||||
|
# we don't have an answer, check optional and default_value
|
||||||
|
if question.value is None or question.value == '':
|
||||||
|
if not question.optional and question.default is None:
|
||||||
|
raise YunohostError('app_argument_required', name=question.name)
|
||||||
|
else:
|
||||||
|
question.value = getattr(self, "default_value", None) if question.default is None else question.default
|
||||||
|
|
||||||
|
# we have an answer, do some post checks
|
||||||
|
if question.value is not None:
|
||||||
|
if question.choices and question.value not in question.choices:
|
||||||
|
self._raise_invalid_answer(question)
|
||||||
|
|
||||||
|
# this is done to enforce a certain formating like for boolean
|
||||||
|
# by default it doesn't do anything
|
||||||
|
question.value = self._post_parse_value(question)
|
||||||
|
|
||||||
|
return (question.value, self.argument_type)
|
||||||
|
|
||||||
|
def _raise_invalid_answer(self, question):
|
||||||
|
raise YunohostError('app_argument_choice_invalid', name=question.name,
|
||||||
|
choices=', '.join(question.choices))
|
||||||
|
|
||||||
|
def _format_text_for_user_input_in_cli(self, question):
|
||||||
|
text_for_user_input_in_cli = _value_for_locale(question.ask)
|
||||||
|
|
||||||
|
if question.choices:
|
||||||
|
text_for_user_input_in_cli += ' [{0}]'.format(' | '.join(question.choices))
|
||||||
|
|
||||||
|
if question.default is not None:
|
||||||
|
text_for_user_input_in_cli += ' (default: {0})'.format(question.default)
|
||||||
|
|
||||||
|
return text_for_user_input_in_cli
|
||||||
|
|
||||||
|
def _post_parse_value(self, question):
|
||||||
|
return question.value
|
||||||
|
|
||||||
|
|
||||||
|
class StringArgumentParser(YunoHostArgumentFormatParser):
|
||||||
|
argument_type = "string"
|
||||||
|
default_value = ""
|
||||||
|
|
||||||
|
|
||||||
|
class PasswordArgumentParser(YunoHostArgumentFormatParser):
|
||||||
|
hide_user_input_in_prompt = True
|
||||||
|
argument_type = "password"
|
||||||
|
default_value = ""
|
||||||
|
forbidden_chars = "{}"
|
||||||
|
|
||||||
|
def parse_question(self, question, user_answers):
|
||||||
|
question = super(PasswordArgumentParser, self).parse_question(question, user_answers)
|
||||||
|
|
||||||
|
if question.default is not None:
|
||||||
|
raise YunohostError('app_argument_password_no_default', name=question.name)
|
||||||
|
|
||||||
|
return question
|
||||||
|
|
||||||
|
def _post_parse_value(self, question):
|
||||||
|
if any(char in question.value for char in self.forbidden_chars):
|
||||||
|
raise YunohostError('pattern_password_app', forbidden_chars=self.forbidden_chars)
|
||||||
|
|
||||||
|
from yunohost.utils.password import assert_password_is_strong_enough
|
||||||
|
assert_password_is_strong_enough('user', question.value)
|
||||||
|
|
||||||
|
return super(PasswordArgumentParser, self)._post_parse_value(question)
|
||||||
|
|
||||||
|
|
||||||
|
class PathArgumentParser(YunoHostArgumentFormatParser):
|
||||||
|
argument_type = "path"
|
||||||
|
default_value = ""
|
||||||
|
|
||||||
|
|
||||||
|
class BooleanArgumentParser(YunoHostArgumentFormatParser):
|
||||||
|
argument_type = "boolean"
|
||||||
|
default_value = False
|
||||||
|
|
||||||
|
def parse_question(self, question, user_answers):
|
||||||
|
question = super(BooleanArgumentParser, self).parse_question(question, user_answers)
|
||||||
|
|
||||||
|
if question.default is None:
|
||||||
|
question.default = False
|
||||||
|
|
||||||
|
return question
|
||||||
|
|
||||||
|
def _format_text_for_user_input_in_cli(self, question):
|
||||||
|
text_for_user_input_in_cli = _value_for_locale(question.ask)
|
||||||
|
|
||||||
|
text_for_user_input_in_cli += " [yes | no]"
|
||||||
|
|
||||||
|
if question.default is not None:
|
||||||
|
formatted_default = "yes" if question.default else "no"
|
||||||
|
text_for_user_input_in_cli += ' (default: {0})'.format(formatted_default)
|
||||||
|
|
||||||
|
return text_for_user_input_in_cli
|
||||||
|
|
||||||
|
def _post_parse_value(self, question):
|
||||||
|
if isinstance(question.value, bool):
|
||||||
|
return 1 if question.value else 0
|
||||||
|
|
||||||
|
if str(question.value).lower() in ["1", "yes", "y"]:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if str(question.value).lower() in ["0", "no", "n"]:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
raise YunohostError('app_argument_choice_invalid', name=question.name,
|
||||||
|
choices='yes, no, y, n, 1, 0')
|
||||||
|
|
||||||
|
|
||||||
|
class DomainArgumentParser(YunoHostArgumentFormatParser):
|
||||||
|
argument_type = "domain"
|
||||||
|
|
||||||
|
def parse_question(self, question, user_answers):
|
||||||
|
from yunohost.domain import domain_list, _get_maindomain
|
||||||
|
|
||||||
|
question = super(DomainArgumentParser, self).parse_question(question, user_answers)
|
||||||
|
|
||||||
|
if question.default is None:
|
||||||
|
question.default = _get_maindomain()
|
||||||
|
|
||||||
|
question.choices = domain_list()["domains"]
|
||||||
|
|
||||||
|
return question
|
||||||
|
|
||||||
|
def _raise_invalid_answer(self, question):
|
||||||
|
raise YunohostError('app_argument_invalid', name=question.name,
|
||||||
|
error=m18n.n('domain_unknown'))
|
||||||
|
|
||||||
|
|
||||||
|
class UserArgumentParser(YunoHostArgumentFormatParser):
|
||||||
|
argument_type = "user"
|
||||||
|
|
||||||
|
def parse_question(self, question, user_answers):
|
||||||
|
from yunohost.user import user_list, user_info
|
||||||
|
from yunohost.domain import _get_maindomain
|
||||||
|
|
||||||
|
question = super(UserArgumentParser, self).parse_question(question, user_answers)
|
||||||
|
question.choices = user_list()["users"]
|
||||||
|
if question.default is None:
|
||||||
|
root_mail = "root@%s" % _get_maindomain()
|
||||||
|
for user in question.choices.keys():
|
||||||
|
if root_mail in user_info(user).get("mail-aliases", []):
|
||||||
|
question.default = user
|
||||||
|
break
|
||||||
|
|
||||||
|
return question
|
||||||
|
|
||||||
|
def _raise_invalid_answer(self, question):
|
||||||
|
raise YunohostError('app_argument_invalid', name=question.name,
|
||||||
|
error=m18n.n('user_unknown', user=question.value))
|
||||||
|
|
||||||
|
|
||||||
|
class AppArgumentParser(YunoHostArgumentFormatParser):
|
||||||
|
argument_type = "app"
|
||||||
|
|
||||||
|
def parse_question(self, question, user_answers):
|
||||||
|
from yunohost.app import app_list
|
||||||
|
|
||||||
|
question = super(AppArgumentParser, self).parse_question(question, user_answers)
|
||||||
|
question.choices = [x["id"] for x in app_list()["apps"]]
|
||||||
|
|
||||||
|
return question
|
||||||
|
|
||||||
|
def _raise_invalid_answer(self, question):
|
||||||
|
raise YunohostError('app_argument_invalid', name=question.name,
|
||||||
|
error=m18n.n('app_unknown'))
|
||||||
|
|
||||||
|
|
||||||
|
class DisplayTextArgumentParser(YunoHostArgumentFormatParser):
|
||||||
|
argument_type = "display_text"
|
||||||
|
|
||||||
|
def parse(self, question, user_answers):
|
||||||
|
print(question["ask"])
|
||||||
|
|
||||||
|
|
||||||
|
ARGUMENTS_TYPE_PARSERS = {
|
||||||
|
"string": StringArgumentParser,
|
||||||
|
"password": PasswordArgumentParser,
|
||||||
|
"path": PathArgumentParser,
|
||||||
|
"boolean": BooleanArgumentParser,
|
||||||
|
"domain": DomainArgumentParser,
|
||||||
|
"user": UserArgumentParser,
|
||||||
|
"app": AppArgumentParser,
|
||||||
|
"display_text": DisplayTextArgumentParser,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _parse_args_in_yunohost_format(user_answers, argument_questions):
|
def _parse_args_in_yunohost_format(user_answers, argument_questions):
|
||||||
"""Parse arguments store in either manifest.json or actions.json or from a
|
"""Parse arguments store in either manifest.json or actions.json or from a
|
||||||
config panel against the user answers when they are present.
|
config panel against the user answers when they are present.
|
||||||
|
@ -2453,128 +2611,14 @@ def _parse_args_in_yunohost_format(user_answers, argument_questions):
|
||||||
format from actions.json/toml, manifest.json/toml
|
format from actions.json/toml, manifest.json/toml
|
||||||
or config_panel.json/toml
|
or config_panel.json/toml
|
||||||
"""
|
"""
|
||||||
from yunohost.domain import domain_list, _get_maindomain
|
|
||||||
from yunohost.user import user_list, user_info
|
|
||||||
|
|
||||||
parsed_answers_dict = OrderedDict()
|
parsed_answers_dict = OrderedDict()
|
||||||
|
|
||||||
for question in argument_questions:
|
for question in argument_questions:
|
||||||
question_name = question['name']
|
parser = ARGUMENTS_TYPE_PARSERS[question.get("type", "string")]()
|
||||||
question_type = question.get('type', 'string')
|
|
||||||
question_default = question.get('default', None)
|
|
||||||
question_choices = question.get('choices', [])
|
|
||||||
question_value = None
|
|
||||||
|
|
||||||
# Transpose default value for boolean type and set it to
|
answer = parser.parse(question=question, user_answers=user_answers)
|
||||||
# false if not defined.
|
if answer is not None:
|
||||||
if question_type == 'boolean':
|
parsed_answers_dict[question["name"]] = answer
|
||||||
question_default = 1 if question_default else 0
|
|
||||||
|
|
||||||
# do not print for webadmin
|
|
||||||
if question_type == 'display_text' and msettings.get('interface') != 'api':
|
|
||||||
print(_value_for_locale(question['ask']))
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Attempt to retrieve argument value
|
|
||||||
if question_name in user_answers:
|
|
||||||
question_value = user_answers[question_name]
|
|
||||||
else:
|
|
||||||
if 'ask' in question:
|
|
||||||
|
|
||||||
if question_type == 'domain':
|
|
||||||
question_default = _get_maindomain()
|
|
||||||
msignals.display(m18n.n('domains_available'))
|
|
||||||
for domain in domain_list()['domains']:
|
|
||||||
msignals.display("- {}".format(domain))
|
|
||||||
|
|
||||||
elif question_type == 'user':
|
|
||||||
msignals.display(m18n.n('users_available'))
|
|
||||||
users = user_list()['users']
|
|
||||||
for user in users.keys():
|
|
||||||
msignals.display("- {}".format(user))
|
|
||||||
|
|
||||||
root_mail = "root@%s" % _get_maindomain()
|
|
||||||
for user in users.keys():
|
|
||||||
if root_mail in user_info(user).get("mail-aliases", []):
|
|
||||||
question_default = user
|
|
||||||
break
|
|
||||||
|
|
||||||
elif question_type == 'password':
|
|
||||||
msignals.display(m18n.n('good_practices_about_user_password'))
|
|
||||||
|
|
||||||
# Retrieve proper ask string
|
|
||||||
text_for_user_input_in_cli = _value_for_locale(question['ask'])
|
|
||||||
|
|
||||||
# Append extra strings
|
|
||||||
if question_type == 'boolean':
|
|
||||||
text_for_user_input_in_cli += ' [yes | no]'
|
|
||||||
elif question_choices:
|
|
||||||
text_for_user_input_in_cli += ' [{0}]'.format(' | '.join(question_choices))
|
|
||||||
|
|
||||||
|
|
||||||
if question_default is not None:
|
|
||||||
if question_type == 'boolean':
|
|
||||||
text_for_user_input_in_cli += ' (default: {0})'.format("yes" if question_default == 1 else "no")
|
|
||||||
else:
|
|
||||||
text_for_user_input_in_cli += ' (default: {0})'.format(question_default)
|
|
||||||
|
|
||||||
is_password = True if question_type == 'password' else False
|
|
||||||
|
|
||||||
try:
|
|
||||||
input_string = msignals.prompt(text_for_user_input_in_cli, is_password)
|
|
||||||
except NotImplementedError:
|
|
||||||
input_string = None
|
|
||||||
if (input_string == '' or input_string is None) \
|
|
||||||
and question_default is not None:
|
|
||||||
question_value = question_default
|
|
||||||
else:
|
|
||||||
question_value = input_string
|
|
||||||
elif question_default is not None:
|
|
||||||
question_value = question_default
|
|
||||||
|
|
||||||
# If the value is empty (none or '')
|
|
||||||
# then check if question is optional or not
|
|
||||||
if question_value is None or question_value == '':
|
|
||||||
if question.get("optional", False):
|
|
||||||
# Argument is optional, keep an empty value
|
|
||||||
# and that's all for this question!
|
|
||||||
parsed_answers_dict[question_name] = ('', question_type)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
# The argument is required !
|
|
||||||
raise YunohostError('app_argument_required', name=question_name)
|
|
||||||
|
|
||||||
# Validate argument choice
|
|
||||||
if question_choices and question_value not in question_choices:
|
|
||||||
raise YunohostError('app_argument_choice_invalid', name=question_name, choices=', '.join(question_choices))
|
|
||||||
|
|
||||||
# Validate argument type
|
|
||||||
if question_type == 'domain':
|
|
||||||
if question_value not in domain_list()['domains']:
|
|
||||||
raise YunohostError('app_argument_invalid', name=question_name, error=m18n.n('domain_unknown'))
|
|
||||||
elif question_type == 'user':
|
|
||||||
if question_value not in user_list()["users"].keys():
|
|
||||||
raise YunohostError('app_argument_invalid', name=question_name, error=m18n.n('user_unknown', user=question_value))
|
|
||||||
elif question_type == 'app':
|
|
||||||
if not _is_installed(question_value):
|
|
||||||
raise YunohostError('app_argument_invalid', name=question_name, error=m18n.n('app_unknown'))
|
|
||||||
elif question_type == 'boolean':
|
|
||||||
if isinstance(question_value, bool):
|
|
||||||
question_value = 1 if question_value else 0
|
|
||||||
else:
|
|
||||||
if str(question_value).lower() in ["1", "yes", "y"]:
|
|
||||||
question_value = 1
|
|
||||||
elif str(question_value).lower() in ["0", "no", "n"]:
|
|
||||||
question_value = 0
|
|
||||||
else:
|
|
||||||
raise YunohostError('app_argument_choice_invalid', name=question_name, choices='yes, no, y, n, 1, 0')
|
|
||||||
elif question_type == 'password':
|
|
||||||
forbidden_chars = "{}"
|
|
||||||
if any(char in question_value for char in forbidden_chars):
|
|
||||||
raise YunohostError('pattern_password_app', forbidden_chars=forbidden_chars)
|
|
||||||
from yunohost.utils.password import assert_password_is_strong_enough
|
|
||||||
assert_password_is_strong_enough('user', question_value)
|
|
||||||
parsed_answers_dict[question_name] = (question_value, question_type)
|
|
||||||
|
|
||||||
return parsed_answers_dict
|
return parsed_answers_dict
|
||||||
|
|
||||||
|
|
|
@ -2188,6 +2188,36 @@ def backup_list(with_info=False, human_readable=False):
|
||||||
return {'archives': archives}
|
return {'archives': archives}
|
||||||
|
|
||||||
|
|
||||||
|
def backup_download(name):
|
||||||
|
|
||||||
|
if msettings.get('interface') != 'api':
|
||||||
|
logger.error("This option is only meant for the API/webadmin and doesn't make sense for the command line.")
|
||||||
|
return
|
||||||
|
|
||||||
|
archive_file = '%s/%s.tar' % (ARCHIVES_PATH, name)
|
||||||
|
|
||||||
|
# Check file exist (even if it's a broken symlink)
|
||||||
|
if not os.path.lexists(archive_file):
|
||||||
|
archive_file += ".gz"
|
||||||
|
if not os.path.lexists(archive_file):
|
||||||
|
raise YunohostError('backup_archive_name_unknown', name=name)
|
||||||
|
|
||||||
|
# If symlink, retrieve the real path
|
||||||
|
if os.path.islink(archive_file):
|
||||||
|
archive_file = os.path.realpath(archive_file)
|
||||||
|
|
||||||
|
# Raise exception if link is broken (e.g. on unmounted external storage)
|
||||||
|
if not os.path.exists(archive_file):
|
||||||
|
raise YunohostError('backup_archive_broken_link',
|
||||||
|
path=archive_file)
|
||||||
|
|
||||||
|
# We return a raw bottle HTTPresponse (instead of serializable data like
|
||||||
|
# list/dict, ...), which is gonna be picked and used directly by moulinette
|
||||||
|
from bottle import static_file
|
||||||
|
archive_folder, archive_file_name = archive_file.rsplit("/", 1)
|
||||||
|
return static_file(archive_file_name, archive_folder, download=archive_file_name)
|
||||||
|
|
||||||
|
|
||||||
def backup_info(name, with_details=False, human_readable=False):
|
def backup_info(name, with_details=False, human_readable=False):
|
||||||
"""
|
"""
|
||||||
Get info about a local backup archive
|
Get info about a local backup archive
|
||||||
|
|
|
@ -36,9 +36,9 @@ class MyMigration(Migration):
|
||||||
raise YunohostError("migration_0017_not_enough_space", path="/var/lib/postgresql/")
|
raise YunohostError("migration_0017_not_enough_space", path="/var/lib/postgresql/")
|
||||||
|
|
||||||
self.runcmd("systemctl stop postgresql")
|
self.runcmd("systemctl stop postgresql")
|
||||||
self.runcmd("pg_dropcluster --stop 11 main || true") # We do not trigger an exception if the command fails because that probably means cluster 11 doesn't exists, which is fine because it's created during the pg_upgradecluster)
|
self.runcmd("LC_ALL=C pg_dropcluster --stop 11 main || true") # We do not trigger an exception if the command fails because that probably means cluster 11 doesn't exists, which is fine because it's created during the pg_upgradecluster)
|
||||||
self.runcmd("pg_upgradecluster -m upgrade 9.6 main")
|
self.runcmd("LC_ALL=C pg_upgradecluster -m upgrade 9.6 main")
|
||||||
self.runcmd("pg_dropcluster --stop 9.6 main")
|
self.runcmd("LC_ALL=C pg_dropcluster --stop 9.6 main")
|
||||||
self.runcmd("systemctl start postgresql")
|
self.runcmd("systemctl start postgresql")
|
||||||
|
|
||||||
def package_is_installed(self, package_name):
|
def package_is_installed(self, package_name):
|
||||||
|
|
|
@ -59,10 +59,29 @@ def domain_list(exclude_subdomains=False):
|
||||||
parent_domain = domain.split(".", 1)[1]
|
parent_domain = domain.split(".", 1)[1]
|
||||||
if parent_domain in result:
|
if parent_domain in result:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
result_list.append(domain)
|
result_list.append(domain)
|
||||||
|
|
||||||
|
def cmp_domain(domain1, domain2):
|
||||||
|
# Keep the main part of the domain and the extension together
|
||||||
|
# eg: this.is.an.example.com -> ['example.com', 'an', 'is', 'this']
|
||||||
|
domain1 = domain1.split('.')
|
||||||
|
domain2 = domain2.split('.')
|
||||||
|
domain1[-1] = domain1[-2] + domain1.pop()
|
||||||
|
domain2[-1] = domain2[-2] + domain2.pop()
|
||||||
|
domain1 = list(reversed(domain1))
|
||||||
|
domain2 = list(reversed(domain2))
|
||||||
|
return cmp(domain1, domain2)
|
||||||
|
|
||||||
|
result_list = sorted(result_list, cmp_domain)
|
||||||
|
|
||||||
return {'domains': result_list}
|
return {'domains': result_list}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'domains': result_list,
|
||||||
|
'main': _get_maindomain()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@is_unit_operation()
|
@is_unit_operation()
|
||||||
def domain_add(operation_logger, domain, dyndns=False):
|
def domain_add(operation_logger, domain, dyndns=False):
|
||||||
|
|
|
@ -36,7 +36,7 @@ from yunohost.log import is_unit_operation
|
||||||
|
|
||||||
logger = getActionLogger('yunohost.user')
|
logger = getActionLogger('yunohost.user')
|
||||||
|
|
||||||
SYSTEM_PERMS = ["mail", "xmpp", "stfp"]
|
SYSTEM_PERMS = ["mail", "xmpp", "sftp", "ssh"]
|
||||||
|
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
|
@ -720,7 +720,7 @@ def _get_journalctl_logs(service, number="all"):
|
||||||
services = _get_services()
|
services = _get_services()
|
||||||
systemd_service = services.get(service, {}).get("actual_systemd_service", service)
|
systemd_service = services.get(service, {}).get("actual_systemd_service", service)
|
||||||
try:
|
try:
|
||||||
return subprocess.check_output("journalctl --no-hostname -xn -u {0} -n{1}".format(systemd_service, number), shell=True)
|
return subprocess.check_output("journalctl --no-hostname --no-pager -u {0} -n{1}".format(systemd_service, number), shell=True)
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
return "error while get services logs from journalctl:\n%s" % traceback.format_exc()
|
return "error while get services logs from journalctl:\n%s" % traceback.format_exc()
|
||||||
|
|
|
@ -69,6 +69,10 @@ DEFAULTS = OrderedDict([
|
||||||
|
|
||||||
("pop3.enabled", {"type": "bool", "default": False}),
|
("pop3.enabled", {"type": "bool", "default": False}),
|
||||||
("smtp.allow_ipv6", {"type": "bool", "default": True}),
|
("smtp.allow_ipv6", {"type": "bool", "default": True}),
|
||||||
|
("smtp.relay.host", {"type": "string", "default": ""}),
|
||||||
|
("smtp.relay.port", {"type": "int", "default": 587}),
|
||||||
|
("smtp.relay.user", {"type": "string", "default": ""}),
|
||||||
|
("smtp.relay.password", {"type": "string", "default": ""}),
|
||||||
("backup.compress_tar_archives", {"type": "bool", "default": False}),
|
("backup.compress_tar_archives", {"type": "bool", "default": False}),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -330,6 +334,10 @@ def reconfigure_ssh(setting_name, old_value, new_value):
|
||||||
|
|
||||||
|
|
||||||
@post_change_hook("smtp.allow_ipv6")
|
@post_change_hook("smtp.allow_ipv6")
|
||||||
|
@post_change_hook("smtp.relay.host")
|
||||||
|
@post_change_hook("smtp.relay.port")
|
||||||
|
@post_change_hook("smtp.relay.user")
|
||||||
|
@post_change_hook("smtp.relay.password")
|
||||||
@post_change_hook("security.postfix.compatibility")
|
@post_change_hook("security.postfix.compatibility")
|
||||||
def reconfigure_postfix(setting_name, old_value, new_value):
|
def reconfigure_postfix(setting_name, old_value, new_value):
|
||||||
if old_value != new_value:
|
if old_value != new_value:
|
||||||
|
|
|
@ -8,7 +8,7 @@ from collections import OrderedDict
|
||||||
from moulinette import msignals
|
from moulinette import msignals
|
||||||
|
|
||||||
from yunohost import domain, user, app
|
from yunohost import domain, user, app
|
||||||
from yunohost.app import _parse_args_in_yunohost_format
|
from yunohost.app import _parse_args_in_yunohost_format, PasswordArgumentParser
|
||||||
from yunohost.utils.error import YunohostError
|
from yunohost.utils.error import YunohostError
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,7 +70,6 @@ def test_parse_args_in_yunohost_format_string_input():
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip # that shit should work x(
|
|
||||||
def test_parse_args_in_yunohost_format_string_input_no_ask():
|
def test_parse_args_in_yunohost_format_string_input_no_ask():
|
||||||
questions = [{"name": "some_string", }]
|
questions = [{"name": "some_string", }]
|
||||||
answers = {}
|
answers = {}
|
||||||
|
@ -96,7 +95,6 @@ def test_parse_args_in_yunohost_format_string_optional_with_input():
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip # this should work without ask
|
|
||||||
def test_parse_args_in_yunohost_format_string_optional_with_input_without_ask():
|
def test_parse_args_in_yunohost_format_string_optional_with_input_without_ask():
|
||||||
questions = [{"name": "some_string", "optional": True, }]
|
questions = [{"name": "some_string", "optional": True, }]
|
||||||
answers = {}
|
answers = {}
|
||||||
|
@ -237,7 +235,6 @@ def test_parse_args_in_yunohost_format_password_input():
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip # that shit should work x(
|
|
||||||
def test_parse_args_in_yunohost_format_password_input_no_ask():
|
def test_parse_args_in_yunohost_format_password_input_no_ask():
|
||||||
questions = [{"name": "some_password", "type": "password", }]
|
questions = [{"name": "some_password", "type": "password", }]
|
||||||
answers = {}
|
answers = {}
|
||||||
|
@ -250,8 +247,9 @@ def test_parse_args_in_yunohost_format_password_input_no_ask():
|
||||||
def test_parse_args_in_yunohost_format_password_no_input_optional():
|
def test_parse_args_in_yunohost_format_password_no_input_optional():
|
||||||
questions = [{"name": "some_password", "type": "password", "optional": True, }]
|
questions = [{"name": "some_password", "type": "password", "optional": True, }]
|
||||||
answers = {}
|
answers = {}
|
||||||
expected_result = OrderedDict({"some_password": ("", "password")})
|
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
with pytest.raises(YunohostError):
|
||||||
|
_parse_args_in_yunohost_format(answers, questions)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_in_yunohost_format_password_optional_with_input():
|
def test_parse_args_in_yunohost_format_password_optional_with_input():
|
||||||
|
@ -270,7 +268,6 @@ def test_parse_args_in_yunohost_format_password_optional_with_input():
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip # this should work without ask
|
|
||||||
def test_parse_args_in_yunohost_format_password_optional_with_input_without_ask():
|
def test_parse_args_in_yunohost_format_password_optional_with_input_without_ask():
|
||||||
questions = [{"name": "some_password", "type": "password", "optional": True, }]
|
questions = [{"name": "some_password", "type": "password", "optional": True, }]
|
||||||
answers = {}
|
answers = {}
|
||||||
|
@ -280,7 +277,6 @@ def test_parse_args_in_yunohost_format_password_optional_with_input_without_ask(
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip # this should raises
|
|
||||||
def test_parse_args_in_yunohost_format_password_no_input_default():
|
def test_parse_args_in_yunohost_format_password_no_input_default():
|
||||||
questions = [
|
questions = [
|
||||||
{
|
{
|
||||||
|
@ -364,6 +360,39 @@ def test_parse_args_in_yunohost_format_password_input_test_ask_with_help():
|
||||||
assert help_text in prompt.call_args[0][0]
|
assert help_text in prompt.call_args[0][0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_args_in_yunohost_format_password_bad_chars():
|
||||||
|
questions = [
|
||||||
|
{
|
||||||
|
"name": "some_password",
|
||||||
|
"type": "password",
|
||||||
|
"ask": "some question",
|
||||||
|
"example": "some_value",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for i in PasswordArgumentParser.forbidden_chars:
|
||||||
|
with pytest.raises(YunohostError):
|
||||||
|
_parse_args_in_yunohost_format({"some_password": i * 8}, questions)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_args_in_yunohost_format_password_strong_enough():
|
||||||
|
questions = [
|
||||||
|
{
|
||||||
|
"name": "some_password",
|
||||||
|
"type": "password",
|
||||||
|
"ask": "some question",
|
||||||
|
"example": "some_value",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
with pytest.raises(YunohostError):
|
||||||
|
# too short
|
||||||
|
_parse_args_in_yunohost_format({"some_password": "a"}, questions)
|
||||||
|
|
||||||
|
with pytest.raises(YunohostError):
|
||||||
|
_parse_args_in_yunohost_format({"some_password": "password"}, questions)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_in_yunohost_format_path():
|
def test_parse_args_in_yunohost_format_path():
|
||||||
questions = [{"name": "some_path", "type": "path", }]
|
questions = [{"name": "some_path", "type": "path", }]
|
||||||
answers = {"some_path": "some_value"}
|
answers = {"some_path": "some_value"}
|
||||||
|
@ -388,7 +417,6 @@ def test_parse_args_in_yunohost_format_path_input():
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip # that shit should work x(
|
|
||||||
def test_parse_args_in_yunohost_format_path_input_no_ask():
|
def test_parse_args_in_yunohost_format_path_input_no_ask():
|
||||||
questions = [{"name": "some_path", "type": "path", }]
|
questions = [{"name": "some_path", "type": "path", }]
|
||||||
answers = {}
|
answers = {}
|
||||||
|
@ -416,7 +444,6 @@ def test_parse_args_in_yunohost_format_path_optional_with_input():
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip # this should work without ask
|
|
||||||
def test_parse_args_in_yunohost_format_path_optional_with_input_without_ask():
|
def test_parse_args_in_yunohost_format_path_optional_with_input_without_ask():
|
||||||
questions = [{"name": "some_path", "type": "path", "optional": True, }]
|
questions = [{"name": "some_path", "type": "path", "optional": True, }]
|
||||||
answers = {}
|
answers = {}
|
||||||
|
@ -604,11 +631,10 @@ def test_parse_args_in_yunohost_format_boolean_input():
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip # we should work
|
|
||||||
def test_parse_args_in_yunohost_format_boolean_input_no_ask():
|
def test_parse_args_in_yunohost_format_boolean_input_no_ask():
|
||||||
questions = [{"name": "some_boolean", "type": "boolean", }]
|
questions = [{"name": "some_boolean", "type": "boolean", }]
|
||||||
answers = {}
|
answers = {}
|
||||||
expected_result = OrderedDict({"some_boolean": ("some_value", "boolean")})
|
expected_result = OrderedDict({"some_boolean": (1, "boolean")})
|
||||||
|
|
||||||
with patch.object(msignals, "prompt", return_value="y"):
|
with patch.object(msignals, "prompt", return_value="y"):
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
@ -660,7 +686,6 @@ def test_parse_args_in_yunohost_format_boolean_no_input_default():
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip # we should raise
|
|
||||||
def test_parse_args_in_yunohost_format_boolean_bad_default():
|
def test_parse_args_in_yunohost_format_boolean_bad_default():
|
||||||
questions = [
|
questions = [
|
||||||
{
|
{
|
||||||
|
@ -704,16 +729,17 @@ def test_parse_args_in_yunohost_format_boolean_input_test_ask_with_default():
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_in_yunohost_format_domain_empty():
|
def test_parse_args_in_yunohost_format_domain_empty():
|
||||||
questions = [{"name": "some_domain", "type": "domain", }]
|
questions = [{"name": "some_domain", "type": "domain",}]
|
||||||
|
main_domain = "my_main_domain.com"
|
||||||
|
expected_result = OrderedDict({"some_domain": (main_domain, "domain")})
|
||||||
answers = {}
|
answers = {}
|
||||||
|
|
||||||
with patch.object(
|
with patch.object(
|
||||||
domain, "_get_maindomain", return_value="my_main_domain.com"
|
domain, "_get_maindomain", return_value="my_main_domain.com"
|
||||||
), patch.object(
|
), patch.object(
|
||||||
domain, "domain_list", return_value={"domains": ["my_main_domain.com"]}
|
domain, "domain_list", return_value={"domains": [main_domain]}
|
||||||
):
|
):
|
||||||
with pytest.raises(YunohostError):
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
_parse_args_in_yunohost_format(answers, questions)
|
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_in_yunohost_format_domain():
|
def test_parse_args_in_yunohost_format_domain():
|
||||||
|
@ -768,7 +794,6 @@ def test_parse_args_in_yunohost_format_domain_two_domains_wrong_answer():
|
||||||
_parse_args_in_yunohost_format(answers, questions)
|
_parse_args_in_yunohost_format(answers, questions)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip # XXX should work
|
|
||||||
def test_parse_args_in_yunohost_format_domain_two_domains_default_no_ask():
|
def test_parse_args_in_yunohost_format_domain_two_domains_default_no_ask():
|
||||||
main_domain = "my_main_domain.com"
|
main_domain = "my_main_domain.com"
|
||||||
other_domain = "some_other_domain.tld"
|
other_domain = "some_other_domain.tld"
|
||||||
|
@ -858,7 +883,8 @@ def test_parse_args_in_yunohost_format_user():
|
||||||
expected_result = OrderedDict({"some_user": (username, "user")})
|
expected_result = OrderedDict({"some_user": (username, "user")})
|
||||||
|
|
||||||
with patch.object(user, "user_list", return_value={"users": users}):
|
with patch.object(user, "user_list", return_value={"users": users}):
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
with patch.object(user, "user_info", return_value={}):
|
||||||
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_in_yunohost_format_user_two_users():
|
def test_parse_args_in_yunohost_format_user_two_users():
|
||||||
|
@ -888,13 +914,15 @@ def test_parse_args_in_yunohost_format_user_two_users():
|
||||||
expected_result = OrderedDict({"some_user": (other_user, "user")})
|
expected_result = OrderedDict({"some_user": (other_user, "user")})
|
||||||
|
|
||||||
with patch.object(user, "user_list", return_value={"users": users}):
|
with patch.object(user, "user_list", return_value={"users": users}):
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
with patch.object(user, "user_info", return_value={}):
|
||||||
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
answers = {"some_user": username}
|
answers = {"some_user": username}
|
||||||
expected_result = OrderedDict({"some_user": (username, "user")})
|
expected_result = OrderedDict({"some_user": (username, "user")})
|
||||||
|
|
||||||
with patch.object(user, "user_list", return_value={"users": users}):
|
with patch.object(user, "user_list", return_value={"users": users}):
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
with patch.object(user, "user_info", return_value={}):
|
||||||
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_in_yunohost_format_user_two_users_wrong_answer():
|
def test_parse_args_in_yunohost_format_user_two_users_wrong_answer():
|
||||||
|
@ -983,13 +1011,14 @@ def test_parse_args_in_yunohost_format_user_two_users_default_input():
|
||||||
answers = {}
|
answers = {}
|
||||||
|
|
||||||
with patch.object(user, "user_list", return_value={"users": users}):
|
with patch.object(user, "user_list", return_value={"users": users}):
|
||||||
expected_result = OrderedDict({"some_user": (username, "user")})
|
with patch.object(user, "user_info", return_value={}):
|
||||||
with patch.object(msignals, "prompt", return_value=username):
|
expected_result = OrderedDict({"some_user": (username, "user")})
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
with patch.object(msignals, "prompt", return_value=username):
|
||||||
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
expected_result = OrderedDict({"some_user": (other_user, "user")})
|
expected_result = OrderedDict({"some_user": (other_user, "user")})
|
||||||
with patch.object(msignals, "prompt", return_value=other_user):
|
with patch.object(msignals, "prompt", return_value=other_user):
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_in_yunohost_format_app_empty():
|
def test_parse_args_in_yunohost_format_app_empty():
|
||||||
|
@ -1020,14 +1049,14 @@ def test_parse_args_in_yunohost_format_app_no_apps():
|
||||||
_parse_args_in_yunohost_format(answers, questions)
|
_parse_args_in_yunohost_format(answers, questions)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip # XXX should work
|
|
||||||
def test_parse_args_in_yunohost_format_app_no_apps_optional():
|
def test_parse_args_in_yunohost_format_app_no_apps_optional():
|
||||||
apps = []
|
apps = []
|
||||||
questions = [{"name": "some_app", "type": "app", "optional": True}]
|
questions = [{"name": "some_app", "type": "app", "optional": True}]
|
||||||
answers = {}
|
answers = {}
|
||||||
|
expected_result = OrderedDict({"some_app": (None, "app")})
|
||||||
|
|
||||||
with patch.object(app, "app_list", return_value={"apps": apps}):
|
with patch.object(app, "app_list", return_value={"apps": apps}):
|
||||||
assert _parse_args_in_yunohost_format(answers, questions) == []
|
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_in_yunohost_format_app():
|
def test_parse_args_in_yunohost_format_app():
|
||||||
|
|
|
@ -33,7 +33,7 @@ from importlib import import_module
|
||||||
from moulinette import msignals, m18n
|
from moulinette import 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, read_yaml, write_to_yaml
|
from moulinette.utils.filesystem import write_to_json, read_yaml, write_to_yaml
|
||||||
|
|
||||||
from yunohost.app import _update_apps_catalog, app_info, app_upgrade, _initialize_apps_catalog_system
|
from yunohost.app import _update_apps_catalog, app_info, app_upgrade, _initialize_apps_catalog_system
|
||||||
from yunohost.domain import domain_add
|
from yunohost.domain import domain_add
|
||||||
|
@ -279,6 +279,9 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
|
||||||
else:
|
else:
|
||||||
dyndns = False
|
dyndns = False
|
||||||
|
|
||||||
|
if os.system("iptables -V >/dev/null 2>/dev/null") != 0:
|
||||||
|
raise YunohostError("iptables/nftables does not seems to be working on your setup. You may be in a container or your kernel does have the proper modules loaded. Sometimes, rebooting the machine may solve the issue.", raw_msg=True)
|
||||||
|
|
||||||
operation_logger.start()
|
operation_logger.start()
|
||||||
logger.info(m18n.n('yunohost_installing'))
|
logger.info(m18n.n('yunohost_installing'))
|
||||||
|
|
||||||
|
@ -303,17 +306,10 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
|
||||||
# Change folders permissions
|
# Change folders permissions
|
||||||
os.system('chmod 755 /home/yunohost.app')
|
os.system('chmod 755 /home/yunohost.app')
|
||||||
|
|
||||||
# Set hostname to avoid amavis bug
|
# Init ssowat's conf.json.persistent
|
||||||
if os.system('hostname -d >/dev/null') != 0:
|
|
||||||
os.system('hostname yunohost.yunohost.org')
|
|
||||||
|
|
||||||
# Add a temporary SSOwat rule to redirect SSO to admin page
|
|
||||||
if not os.path.exists('/etc/ssowat/conf.json.persistent'):
|
if not os.path.exists('/etc/ssowat/conf.json.persistent'):
|
||||||
ssowat_conf = {}
|
write_to_json('/etc/ssowat/conf.json.persistent', {})
|
||||||
else:
|
|
||||||
ssowat_conf = read_json('/etc/ssowat/conf.json.persistent')
|
|
||||||
|
|
||||||
write_to_json('/etc/ssowat/conf.json.persistent', ssowat_conf)
|
|
||||||
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
|
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
|
||||||
|
|
||||||
# Create SSL CA
|
# Create SSL CA
|
||||||
|
@ -915,44 +911,12 @@ def tools_migrations_state():
|
||||||
"""
|
"""
|
||||||
Show current migration state
|
Show current migration state
|
||||||
"""
|
"""
|
||||||
if os.path.exists("/etc/yunohost/migrations_state.json"):
|
|
||||||
_migrate_legacy_migration_json()
|
|
||||||
|
|
||||||
if not os.path.exists(MIGRATIONS_STATE_PATH):
|
if not os.path.exists(MIGRATIONS_STATE_PATH):
|
||||||
return {"migrations": {}}
|
return {"migrations": {}}
|
||||||
|
|
||||||
return read_yaml(MIGRATIONS_STATE_PATH)
|
return read_yaml(MIGRATIONS_STATE_PATH)
|
||||||
|
|
||||||
|
|
||||||
def _migrate_legacy_migration_json():
|
|
||||||
|
|
||||||
from moulinette.utils.filesystem import read_json
|
|
||||||
|
|
||||||
logger.debug("Migrating legacy migration state json to yaml...")
|
|
||||||
|
|
||||||
# We fetch the old state containing the last run migration
|
|
||||||
old_state = read_json("/etc/yunohost/migrations_state.json")["last_run_migration"]
|
|
||||||
last_run_migration_id = str(old_state["number"]) + "_" + old_state["name"]
|
|
||||||
|
|
||||||
# Extract the list of migration ids
|
|
||||||
from . import data_migrations
|
|
||||||
migrations_path = data_migrations.__path__[0]
|
|
||||||
migration_files = filter(lambda x: re.match(r"^\d+_[a-zA-Z0-9_]+\.py$", x), os.listdir(migrations_path))
|
|
||||||
# (here we remove the .py extension and make sure the ids are sorted)
|
|
||||||
migration_ids = sorted([f.rsplit(".", 1)[0] for f in migration_files])
|
|
||||||
|
|
||||||
# So now build the new dict for every id up to the last run migration
|
|
||||||
migrations = {}
|
|
||||||
for migration_id in migration_ids:
|
|
||||||
migrations[migration_id] = "done"
|
|
||||||
if last_run_migration_id in migration_id:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Write the new file and rename the old one
|
|
||||||
write_to_yaml(MIGRATIONS_STATE_PATH, {"migrations": migrations})
|
|
||||||
os.rename("/etc/yunohost/migrations_state.json", "/etc/yunohost/migrations_state.json.old")
|
|
||||||
|
|
||||||
|
|
||||||
def _write_migration_state(migration_id, state):
|
def _write_migration_state(migration_id, state):
|
||||||
|
|
||||||
current_states = tools_migrations_state()
|
current_states = tools_migrations_state()
|
||||||
|
|
Loading…
Add table
Reference in a new issue