Merge branch 'stretch-unstable' into fix-1516-separate-xmpp-vhosts

This commit is contained in:
Alexandre Aubin 2020-04-27 18:03:34 +02:00 committed by GitHub
commit ceab9bd1b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 2829 additions and 774 deletions

View file

@ -179,6 +179,10 @@ def _retrieve_namespaces():
ret.append(n)
return ret
# Stupid PATH management because sometimes (e.g. some cron job) PATH is only /usr/bin:/bin ...
default_path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
if os.environ["PATH"] != default_path:
os.environ["PATH"] = default_path + ":" + os.environ["PATH"]
# Main action ----------------------------------------------------------

View file

@ -296,6 +296,13 @@ user:
help: Display all info known about each permission, including the full user list of each group it is granted to.
action: store_true
### user_permission_info()
info:
action_help: Get information about a specific permission
api: GET /users/permissions/<permission>
arguments:
permission:
help: Name of the permission to fetch info about
### user_permission_update()
update:
@ -560,6 +567,9 @@ app:
help: Also return a list of app categories
action: store_true
fetchlist:
deprecated: true
### app_list()
list:
action_help: List installed apps
@ -569,6 +579,12 @@ app:
full: --full
help: Display all details, including the app manifest and various other infos
action: store_true
-i:
full: --installed
help: Dummy argument, does nothing anymore (still there only for backward compatibility)
action: store_true
filter:
nargs: '?'
### app_info()
info:
@ -1038,6 +1054,7 @@ service:
### service_restart()
restart:
action_help: Restart one or more services. If the services are not running yet, they will be started.
api: PUT /services/<names>/restart
arguments:
names:
help: Service name to restart
@ -1456,6 +1473,11 @@ tools:
help: List pending configuration files and exit
action: store_true
### tools_versions()
versions:
action_help: Display YunoHost's packages versions
api: GET /versions
subcategories:
migrations:
@ -1668,7 +1690,7 @@ diagnosis:
action: store_true
run:
action_help: Show most recents diagnosis results
action_help: Run diagnosis
api: POST /diagnosis/run
arguments:
categories:
@ -1677,6 +1699,9 @@ diagnosis:
--force:
help: Ignore the cached report even if it is still 'fresh'
action: store_true
--except-if-never-ran-yet:
help: Don't run anything if diagnosis never ran yet ... (this is meant to be used by the webadmin)
action: store_true
ignore:
action_help: Configure some diagnosis results to be ignored and therefore not considered as actual issues
@ -1693,3 +1718,14 @@ diagnosis:
--list:
help: List active ignore filters
action: store_true
get:
action_help: Low-level command to fetch raw data and status about a specific diagnosis test
api: GET /diagnosis/item/<category>
arguments:
category:
help: Diagnosis category to fetch results from
item:
help: "List of criteria describing the test. Must correspond exactly to the 'meta' infos in 'yunohost diagnosis show'"
metavar: CRITERIA
nargs: "*"

View file

@ -3,7 +3,7 @@ Simple automated generation of a bash_completion file
for yunohost command from the actionsmap.
Generates a bash completion file assuming the structure
`yunohost domain action`
`yunohost category action`
adds `--help` at the end if one presses [tab] again.
author: Christophe Vuillot
@ -15,18 +15,39 @@ THIS_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
ACTIONSMAP_FILE = THIS_SCRIPT_DIR + '/yunohost.yml'
BASH_COMPLETION_FILE = THIS_SCRIPT_DIR + '/../bash-completion.d/yunohost'
def get_dict_actions(OPTION_SUBTREE, category):
ACTIONS = [action for action in OPTION_SUBTREE[category]["actions"].keys()
if not action.startswith('_')]
ACTIONS_STR = '{}'.format(' '.join(ACTIONS))
DICT = { "actions_str": ACTIONS_STR }
return DICT
with open(ACTIONSMAP_FILE, 'r') as stream:
# Getting the dictionary containning what actions are possible per domain
# Getting the dictionary containning what actions are possible per category
OPTION_TREE = yaml.load(stream)
DOMAINS = [str for str in OPTION_TREE.keys() if not str.startswith('_')]
DOMAINS_STR = '"{}"'.format(' '.join(DOMAINS))
CATEGORY = [category for category in OPTION_TREE.keys() if not category.startswith('_')]
CATEGORY_STR = '{}'.format(' '.join(CATEGORY))
ACTIONS_DICT = {}
for domain in DOMAINS:
ACTIONS = [str for str in OPTION_TREE[domain]['actions'].keys()
if not str.startswith('_')]
ACTIONS_STR = '"{}"'.format(' '.join(ACTIONS))
ACTIONS_DICT[domain] = ACTIONS_STR
for category in CATEGORY:
ACTIONS_DICT[category] = get_dict_actions(OPTION_TREE, category)
ACTIONS_DICT[category]["subcategories"] = {}
ACTIONS_DICT[category]["subcategories_str"] = ""
if "subcategories" in OPTION_TREE[category].keys():
SUBCATEGORIES = [ subcategory for subcategory in OPTION_TREE[category]["subcategories"].keys() ]
SUBCATEGORIES_STR = '{}'.format(' '.join(SUBCATEGORIES))
ACTIONS_DICT[category]["subcategories_str"] = SUBCATEGORIES_STR
for subcategory in SUBCATEGORIES:
ACTIONS_DICT[category]["subcategories"][subcategory] = get_dict_actions(OPTION_TREE[category]["subcategories"], subcategory)
with open(BASH_COMPLETION_FILE, 'w') as generated_file:
@ -47,31 +68,49 @@ with open(ACTIONSMAP_FILE, 'r') as stream:
generated_file.write('\tnarg=${#COMP_WORDS[@]}\n\n')
generated_file.write('\t# the current word being typed\n')
generated_file.write('\tcur="${COMP_WORDS[COMP_CWORD]}"\n\n')
generated_file.write('\t# the last typed word\n')
generated_file.write('\tprev="${COMP_WORDS[COMP_CWORD-1]}"\n\n')
# If one is currently typing a domain then match with the domain list
generated_file.write('\t# If one is currently typing a domain,\n')
generated_file.write('\t# match with domains\n')
# If one is currently typing a category then match with the category list
generated_file.write('\t# If one is currently typing a category,\n')
generated_file.write('\t# match with categorys\n')
generated_file.write('\tif [[ $narg == 2 ]]; then\n')
generated_file.write('\t\topts={}\n'.format(DOMAINS_STR))
generated_file.write('\t\topts="{}"\n'.format(CATEGORY_STR))
generated_file.write('\tfi\n\n')
# If one is currently typing an action then match with the action list
# of the previously typed domain
generated_file.write('\t# If one already typed a domain,\n')
generated_file.write('\t# match the actions of that domain\n')
# of the previously typed category
generated_file.write('\t# If one already typed a category,\n')
generated_file.write('\t# match the actions or the subcategories of that category\n')
generated_file.write('\tif [[ $narg == 3 ]]; then\n')
for domain in DOMAINS:
generated_file.write('\t\tif [[ $prev == "{}" ]]; then\n'.format(domain))
generated_file.write('\t\t\topts={}\n'.format(ACTIONS_DICT[domain]))
generated_file.write('\t\t# the category typed\n')
generated_file.write('\t\tcategory="${COMP_WORDS[1]}"\n\n')
for category in CATEGORY:
generated_file.write('\t\tif [[ $category == "{}" ]]; then\n'.format(category))
generated_file.write('\t\t\topts="{} {}"\n'.format(ACTIONS_DICT[category]["actions_str"], ACTIONS_DICT[category]["subcategories_str"]))
generated_file.write('\t\tfi\n')
generated_file.write('\tfi\n\n')
# If both domain and action have been typed or the domain
generated_file.write('\t# If one already typed an action or a subcategory,\n')
generated_file.write('\t# match the actions of that subcategory\n')
generated_file.write('\tif [[ $narg == 4 ]]; then\n')
generated_file.write('\t\t# the category typed\n')
generated_file.write('\t\tcategory="${COMP_WORDS[1]}"\n\n')
generated_file.write('\t\t# the action or the subcategory typed\n')
generated_file.write('\t\taction_or_subcategory="${COMP_WORDS[2]}"\n\n')
for category in CATEGORY:
if len(ACTIONS_DICT[category]["subcategories"]):
generated_file.write('\t\tif [[ $category == "{}" ]]; then\n'.format(category))
for subcategory in ACTIONS_DICT[category]["subcategories"]:
generated_file.write('\t\t\tif [[ $action_or_subcategory == "{}" ]]; then\n'.format(subcategory))
generated_file.write('\t\t\t\topts="{}"\n'.format(ACTIONS_DICT[category]["subcategories"][subcategory]["actions_str"]))
generated_file.write('\t\t\tfi\n')
generated_file.write('\t\tfi\n')
generated_file.write('\tfi\n\n')
# If both category and action have been typed or the category
# was not recognized propose --help (only once)
generated_file.write('\t# If no options were found propose --help\n')
generated_file.write('\tif [ -z "$opts" ]; then\n')
generated_file.write('\t\tprev="${COMP_WORDS[COMP_CWORD-1]}"\n\n')
generated_file.write('\t\tif [[ $prev != "--help" ]]; then\n')
generated_file.write('\t\t\topts=( --help )\n')
generated_file.write('\t\tfi\n')

View file

@ -189,7 +189,16 @@ ynh_package_install_from_equivs () {
# If install fails we use "apt-get check" to try to debug and diagnose possible unmet dependencies
# Note the use of { } which allows to group commands without starting a subshell (otherwise the ynh_die wouldn't exit the current shell).
# Be careful with the syntax : the semicolon + space at the end is important!
ynh_package_install -f || { apt-get check 2>&1; ynh_die --message="Unable to install dependencies"; }
ynh_package_install -f || \
{ # If the installation failed
# Get the list of dependencies from the deb
local dependencies="$(dpkg --info "$TMPDIR/${pkgname}_${pkgversion}_all.deb" | grep Depends | \
sed 's/^ Depends: //' | sed 's/,//g')"
# Fake an install of those dependencies to see the errors
# The sed command here is, Print only from '--fix-broken' to the end.
ynh_package_install $dependencies --dry-run | sed -n '/--fix-broken/,$p' >&2
ynh_die --message="Unable to install dependencies"; }
[[ -n "$TMPDIR" ]] && rm -rf $TMPDIR # Remove the temp dir.
# check if the package is actually installed
@ -208,7 +217,8 @@ ynh_package_install_from_equivs () {
# Requires YunoHost version 2.6.4 or higher.
ynh_install_app_dependencies () {
local dependencies=$@
local dependencies=${dependencies// /, }
# Add a comma for each space between packages. But not add a comma if the space separate a version specification. (See below)
dependencies="$(echo "$dependencies" | sed 's/\([^\<=\>]\)\ \([^(]\)/\1, \2/g')"
local dependencies=${dependencies//|/ | }
local manifest_path="../manifest.json"
if [ ! -e "$manifest_path" ]; then
@ -221,6 +231,20 @@ ynh_install_app_dependencies () {
fi
local dep_app=${app//_/-} # Replace all '_' by '-'
# Handle specific versions
if [[ "$dependencies" =~ [\<=\>] ]]
then
# Replace version specifications by relationships syntax
# https://www.debian.org/doc/debian-policy/ch-relationships.html
# Sed clarification
# [^(\<=\>] ignore if it begins by ( or < = >. To not apply twice.
# [\<=\>] matches < = or >
# \+ matches one or more occurence of the previous characters, for >= or >>.
# [^,]\+ matches all characters except ','
# Ex: 'package>=1.0' will be replaced by 'package (>= 1.0)'
dependencies="$(echo "$dependencies" | sed 's/\([^(\<=\>]\)\([\<=\>]\+\)\([^,]\+\)/\1 (\2 \3)/g')"
fi
#
# Epic ugly hack to fix the goddamn dependency nightmare of sury
# Sponsored by the "Djeezusse Fokin Kraiste Why Do Adminsys Has To Be So Fucking Complicated I Should Go Grow Potatoes Instead Of This Shit" collective
@ -236,8 +260,11 @@ ynh_install_app_dependencies () {
if ! grep -nrq "sury" /etc/apt/sources.list*
then
# Re-add sury
echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/sury.list
wget -O /etc/apt/trusted.gpg.d/sury.gpg https://packages.sury.org/php/apt.gpg
ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(lsb_release -sc) main" --key="https://packages.sury.org/php/apt.gpg" --name=extra_php_version
# Pin this sury repository to prevent sury of doing shit
ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version
ynh_pin_repo --package="php${$YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append
fi
fi
fi
@ -258,6 +285,38 @@ EOF
ynh_app_setting_set --app=$app --key=apt_dependencies --value="$dependencies"
}
# Add dependencies to install with ynh_install_app_dependencies
#
# [internal]
#
# usage: ynh_add_app_dependencies --package=phpversion [--replace]
# | arg: -p, --package - Packages to add as dependencies for the app.
# | arg: -r, --replace - Replace dependencies instead of adding to existing ones.
ynh_add_app_dependencies () {
# Declare an array to define the options of this helper.
local legacy_args=pr
declare -Ar args_array=( [p]=package= [r]=replace)
local package
local replace
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
replace=${replace:-0}
local current_dependencies=""
if [ $replace -eq 0 ]
then
local dep_app=${app//_/-} # Replace all '_' by '-'
if ynh_package_is_installed --package="${dep_app}-ynh-deps"
then
current_dependencies="$(dpkg-query --show --showformat='${Depends}' ${dep_app}-ynh-deps) "
fi
current_dependencies=${current_dependencies// | /|}
fi
ynh_install_app_dependencies "${current_dependencies}${package}"
}
# Remove fake package and its dependencies
#
# Dependencies will removed only if no other package need them.
@ -269,3 +328,223 @@ ynh_remove_app_dependencies () {
local dep_app=${app//_/-} # Replace all '_' by '-'
ynh_package_autopurge ${dep_app}-ynh-deps # Remove the fake package and its dependencies if they not still used.
}
#=================================================
# Install packages from an extra repository properly.
#
# usage: ynh_install_extra_app_dependencies --repo="repo" --package="dep1 dep2" [--key=key_url] [--name=name]
# | arg: -r, --repo - Complete url of the extra repository.
# | arg: -p, --package - The packages to install from this extra repository
# | arg: -k, --key - url to get the public key.
# | arg: -n, --name - Name for the files for this repo, $app as default value.
ynh_install_extra_app_dependencies () {
# Declare an array to define the options of this helper.
local legacy_args=rpkn
declare -Ar args_array=( [r]=repo= [p]=package= [k]=key= [n]=name= )
local repo
local package
local key
local name
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
name="${name:-$app}"
key=${key:-}
# Set a key only if asked
if [ -n "$key" ]
then
key="--key=$key"
fi
# Add an extra repository for those packages
ynh_install_extra_repo --repo="$repo" $key --priority=995 --name=$name
# Install requested dependencies from this extra repository.
ynh_add_app_dependencies --package="$package"
# Remove this extra repository after packages are installed
ynh_remove_extra_repo --name=$app
}
# Add an extra repository correctly, pin it and get the key.
#
# [internal]
#
# usage: ynh_install_extra_repo --repo="repo" [--key=key_url] [--priority=priority_value] [--name=name] [--append]
# | arg: -r, --repo - Complete url of the extra repository.
# | arg: -k, --key - url to get the public key.
# | arg: -p, --priority - Priority for the pin
# | arg: -n, --name - Name for the files for this repo, $app as default value.
# | arg: -a, --append - Do not overwrite existing files.
ynh_install_extra_repo () {
# Declare an array to define the options of this helper.
local legacy_args=rkpna
declare -Ar args_array=( [r]=repo= [k]=key= [p]=priority= [n]=name= [a]=append )
local repo
local key
local priority
local name
local append
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
name="${name:-$app}"
append=${append:-0}
key=${key:-}
priority=${priority:-}
if [ $append -eq 1 ]
then
append="--append"
wget_append="tee -a"
else
append=""
wget_append="tee"
fi
# Split the repository into uri, suite and components.
# Remove "deb " at the beginning of the repo.
repo="${repo#deb }"
# Get the uri
local uri="$(echo "$repo" | awk '{ print $1 }')"
# Get the suite
local suite="$(echo "$repo" | awk '{ print $2 }')"
# Get the components
local component="${repo##$uri $suite }"
# Add the repository into sources.list.d
ynh_add_repo --uri="$uri" --suite="$suite" --component="$component" --name="$name" $append
# Pin the new repo with the default priority, so it won't be used for upgrades.
# Build $pin from the uri without http and any sub path
local pin="${uri#*://}"
pin="${pin%%/*}"
# Set a priority only if asked
if [ -n "$priority" ]
then
priority="--priority=$priority"
fi
ynh_pin_repo --package="*" --pin="origin \"$pin\"" $priority --name="$name" $append
# Get the public key for the repo
if [ -n "$key" ]
then
mkdir -p "/etc/apt/trusted.gpg.d"
wget -q "$key" -O - | gpg --dearmor | $wget_append /etc/apt/trusted.gpg.d/$name.gpg > /dev/null
fi
# Update the list of package with the new repo
ynh_package_update
}
# Remove an extra repository and the assiociated configuration.
#
# [internal]
#
# usage: ynh_remove_extra_repo [--name=name]
# | arg: -n, --name - Name for the files for this repo, $app as default value.
ynh_remove_extra_repo () {
# Declare an array to define the options of this helper.
local legacy_args=n
declare -Ar args_array=( [n]=name= )
local name
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
name="${name:-$app}"
ynh_secure_remove "/etc/apt/sources.list.d/$name.list"
ynh_secure_remove "/etc/apt/preferences.d/$name"
ynh_secure_remove "/etc/apt/trusted.gpg.d/$name.gpg"
ynh_secure_remove "/etc/apt/trusted.gpg.d/$name.asc"
# Update the list of package to exclude the old repo
ynh_package_update
}
# Add a repository.
#
# [internal]
#
# usage: ynh_add_repo --uri=uri --suite=suite --component=component [--name=name] [--append]
# | arg: -u, --uri - Uri of the repository.
# | arg: -s, --suite - Suite of the repository.
# | arg: -c, --component - Component of the repository.
# | arg: -n, --name - Name for the files for this repo, $app as default value.
# | arg: -a, --append - Do not overwrite existing files.
#
# Example for a repo like deb http://forge.yunohost.org/debian/ stretch stable
# uri suite component
# ynh_add_repo --uri=http://forge.yunohost.org/debian/ --suite=stretch --component=stable
#
ynh_add_repo () {
# Declare an array to define the options of this helper.
local legacy_args=uscna
declare -Ar args_array=( [u]=uri= [s]=suite= [c]=component= [n]=name= [a]=append )
local uri
local suite
local component
local name
local append
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
name="${name:-$app}"
append=${append:-0}
if [ $append -eq 1 ]
then
append="tee -a"
else
append="tee"
fi
mkdir -p "/etc/apt/sources.list.d"
# Add the new repo in sources.list.d
echo "deb $uri $suite $component" \
| $append "/etc/apt/sources.list.d/$name.list"
}
# Pin a repository.
#
# [internal]
#
# usage: ynh_pin_repo --package=packages --pin=pin_filter [--priority=priority_value] [--name=name] [--append]
# | arg: -p, --package - Packages concerned by the pin. Or all, *.
# | arg: -i, --pin - Filter for the pin.
# | arg: -p, --priority - Priority for the pin
# | arg: -n, --name - Name for the files for this repo, $app as default value.
# | arg: -a, --append - Do not overwrite existing files.
#
# See https://manpages.debian.org/stretch/apt/apt_preferences.5.en.html#How_APT_Interprets_Priorities for information about pinning.
#
ynh_pin_repo () {
# Declare an array to define the options of this helper.
local legacy_args=pirna
declare -Ar args_array=( [p]=package= [i]=pin= [r]=priority= [n]=name= [a]=append )
local package
local pin
local priority
local name
local append
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
package="${package:-*}"
priority=${priority:-50}
name="${name:-$app}"
append=${append:-0}
if [ $append -eq 1 ]
then
append="tee -a"
else
append="tee"
fi
mkdir -p "/etc/apt/preferences.d"
echo "Package: $package
Pin: $pin
Pin-Priority: $priority
" \
| $append "/etc/apt/preferences.d/$name"
}

View file

@ -147,6 +147,9 @@ ynh_handle_getopts_args () {
break
fi
else
# Ignore empty parameters
if [ -n "${all_args[$i]}" ]
then
# Else, add this value to this option
# Each value will be separated by ';'
if [ -n "${!option_var}" ]
@ -166,6 +169,7 @@ ynh_handle_getopts_args () {
# So... Stop fucking arguing each time that eval is evil... Go find an other working solution if you can find one!
eval ${option_var}+='"${all_args[$i]}"'
fi
shift_value=$(( shift_value + 1 ))
fi
done

102
data/helpers.d/hardware Normal file
View file

@ -0,0 +1,102 @@
#!/bin/bash
# Get the total or free amount of RAM+swap on the system
#
# usage: ynh_get_ram [--free|--total] [--ignore_swap|--only_swap]
# | arg: -f, --free - Count free RAM+swap
# | arg: -t, --total - Count total RAM+swap
# | arg: -s, --ignore_swap - Ignore swap, consider only real RAM
# | arg: -o, --only_swap - Ignore real RAM, consider only swap
ynh_get_ram () {
# Declare an array to define the options of this helper.
local legacy_args=ftso
declare -Ar args_array=( [f]=free [t]=total [s]=ignore_swap [o]=only_swap )
local free
local total
local ignore_swap
local only_swap
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
ignore_swap=${ignore_swap:-0}
only_swap=${only_swap:-0}
free=${free:-0}
total=${total:-0}
local total_ram=$(vmstat --stats --unit M | grep "total memory" | awk '{print $1}')
local total_swap=$(vmstat --stats --unit M | grep "total swap" | awk '{print $1}')
local total_ram_swap=$(( total_ram + total_swap ))
local free_ram=$(vmstat --stats --unit M | grep "free memory" | awk '{print $1}')
local free_swap=$(vmstat --stats --unit M | grep "free swap" | awk '{print $1}')
local free_ram_swap=$(( free_ram + free_swap ))
# Use the total amount of ram
if [ $free -eq 1 ]
then
# Use the total amount of free ram
local ram=$free_ram_swap
if [ $ignore_swap -eq 1 ]
then
# Use only the amount of free ram
ram=$free_ram
elif [ $only_swap -eq 1 ]
then
# Use only the amount of free swap
ram=$free_swap
fi
elif [ $total -eq 1 ]
then
local ram=$total_ram_swap
if [ $ignore_swap -eq 1 ]
then
# Use only the amount of free ram
ram=$total_ram
elif [ $only_swap -eq 1 ]
then
# Use only the amount of free swap
ram=$total_swap
fi
else
ynh_print_warn --message="You have to choose --free or --total when using ynh_get_ram"
ram=0
fi
echo $ram
}
# Return 0 or 1 depending if the system has a given amount of RAM+swap free or total
#
# usage: ynh_require_ram --required=RAM required in Mb [--free|--total] [--ignore_swap|--only_swap]
# | arg: -r, --required - The amount to require, in Mb
# | arg: -f, --free - Count free RAM+swap
# | arg: -t, --total - Count total RAM+swap
# | arg: -s, --ignore_swap - Ignore swap, consider only real RAM
# | arg: -o, --only_swap - Ignore real RAM, consider only swap
ynh_require_ram () {
# Declare an array to define the options of this helper.
local legacy_args=rftso
declare -Ar args_array=( [r]=required= [f]=free [t]=total [s]=ignore_swap [o]=only_swap )
local required
local free
local total
local ignore_swap
local only_swap
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
# Dunno if that's the right way to do, but that's some black magic to be able to
# forward the bool args to ynh_get_ram easily?
# If the variable $free is not empty, set it to '--free'
free=${free:+--free}
total=${total:+--total}
ignore_swap=${ignore_swap:+--ignore_swap}
only_swap=${only_swap:+--only_swap}
local ram=$(ynh_get_ram $free $total $ignore_swap $only_swap)
if [ $ram -lt $required ]
then
return 1
else
return 0
fi
}

View file

@ -12,6 +12,7 @@
# __PORT__ by $port
# __NAME__ by $app
# __FINALPATH__ by $final_path
# __PHPVERSION__ by $YNH_PHP_VERSION ($YNH_PHP_VERSION is either the default php version or the version defined for the app)
#
# And dynamic variables (from the last example) :
# __PATH_2__ by $path_2
@ -44,6 +45,7 @@ ynh_add_nginx_config () {
if test -n "${final_path:-}"; then
ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalnginxconf"
fi
ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$YNH_PHP_VERSION" --target_file="$finalnginxconf"
# Replace all other variable given as arguments
for var_to_replace in $others_var

View file

@ -1,51 +1,260 @@
#!/bin/bash
readonly YNH_DEFAULT_PHP_VERSION=7.0
# Declare the actual php version to use.
# A packager willing to use another version of php can override the variable into its _common.sh.
YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION}
# Create a dedicated php-fpm config
#
# usage: ynh_add_fpm_config [--phpversion=7.X]
# usage 1: ynh_add_fpm_config [--phpversion=7.X] [--use_template] [--package=packages] [--dedicated_service]
# | arg: -v, --phpversion - Version of php to use.
# | arg: -t, --use_template - Use this helper in template mode.
# | arg: -p, --package - Additionnal php packages to install
# | arg: -d, --dedicated_service - Use a dedicated php-fpm service instead of the common one.
#
# -----------------------------------------------------------------------------
#
# usage 2: ynh_add_fpm_config [--phpversion=7.X] --usage=usage --footprint=footprint [--package=packages] [--dedicated_service]
# | arg: -v, --phpversion - Version of php to use.
# | arg: -f, --footprint - Memory footprint of the service (low/medium/high).
# low - Less than 20Mb of ram by pool.
# medium - Between 20Mb and 40Mb of ram by pool.
# high - More than 40Mb of ram by pool.
# Or specify exactly the footprint, the load of the service as Mb by pool instead of having a standard value.
# To have this value, use the following command and stress the service.
# watch -n0.5 ps -o user,cmd,%cpu,rss -u APP
#
# | arg: -u, --usage - Expected usage of the service (low/medium/high).
# low - Personal usage, behind the sso.
# medium - Low usage, few people or/and publicly accessible.
# high - High usage, frequently visited website.
#
# | arg: -p, --package - Additionnal php packages to install for a specific version of php
# | arg: -d, --dedicated_service - Use a dedicated php-fpm service instead of the common one.
#
#
# The footprint of the service will be used to defined the maximum footprint we can allow, which is half the maximum RAM.
# So it will be used to defined 'pm.max_children'
# A lower value for the footprint will allow more children for 'pm.max_children'. And so for
# 'pm.start_servers', 'pm.min_spare_servers' and 'pm.max_spare_servers' which are defined from the
# value of 'pm.max_children'
# NOTE: 'pm.max_children' can't exceed 4 times the number of processor's cores.
#
# The usage value will defined the way php will handle the children for the pool.
# A value set as 'low' will set the process manager to 'ondemand'. Children will start only if the
# service is used, otherwise no child will stay alive. This config gives the lower footprint when the
# service is idle. But will use more proc since it has to start a child as soon it's used.
# Set as 'medium', the process manager will be at dynamic. If the service is idle, a number of children
# equal to pm.min_spare_servers will stay alive. So the service can be quick to answer to any request.
# The number of children can grow if needed. The footprint can stay low if the service is idle, but
# not null. The impact on the proc is a little bit less than 'ondemand' as there's always a few
# children already available.
# Set as 'high', the process manager will be set at 'static'. There will be always as many children as
# 'pm.max_children', the footprint is important (but will be set as maximum a quarter of the maximum
# RAM) but the impact on the proc is lower. The service will be quick to answer as there's always many
# children ready to answer.
#
# Requires YunoHost version 2.7.2 or higher.
ynh_add_fpm_config () {
# Declare an array to define the options of this helper.
local legacy_args=v
declare -Ar args_array=( [v]=phpversion= )
local legacy_args=vtufpd
declare -Ar args_array=( [v]=phpversion= [t]=use_template [u]=usage= [f]=footprint= [p]=package= [d]=dedicated_service )
local phpversion
local use_template
local usage
local footprint
local package
local dedicated_service
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
package=${package:-}
# Configure PHP-FPM 7.0 by default
phpversion="${phpversion:-7.0}"
# The default behaviour is to use the template.
use_template="${use_template:-1}"
usage="${usage:-}"
footprint="${footprint:-}"
if [ -n "$usage" ] || [ -n "$footprint" ]; then
use_template=0
fi
# Do not use a dedicated service by default
dedicated_service=${dedicated_service:-0}
local fpm_config_dir="/etc/php/$phpversion/fpm"
# Set the default PHP-FPM version by default
phpversion="${phpversion:-$YNH_PHP_VERSION}"
# If the requested php version is not the default version for YunoHost
if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ]
then
# If the argument --package is used, add the packages to ynh_install_php to install them from sury
if [ -n "$package" ]; then
local additionnal_packages="--package=$package"
else
local additionnal_packages=""
fi
# Install this specific version of php.
ynh_install_php --phpversion=$phpversion "$additionnal_packages"
elif [ -n "$package" ]
then
# Install the additionnal packages from the default repository
ynh_add_app_dependencies --package="$package"
fi
if [ $dedicated_service -eq 1 ]
then
local fpm_service="${app}-phpfpm"
local fpm_config_dir="/etc/php/$phpversion/dedicated-fpm"
else
local fpm_service="php${phpversion}-fpm"
local fpm_config_dir="/etc/php/$phpversion/fpm"
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
mkdir -p "$fpm_config_dir/pool.d"
ynh_app_setting_set --app=$app --key=fpm_config_dir --value="$fpm_config_dir"
ynh_app_setting_set --app=$app --key=fpm_service --value="$fpm_service"
ynh_app_setting_set --app=$app --key=fpm_dedicated_service --value="$dedicated_service"
ynh_app_setting_set --app=$app --key=phpversion --value=$phpversion
finalphpconf="$fpm_config_dir/pool.d/$app.conf"
# Migrate from mutual php service to dedicated one.
if [ $dedicated_service -eq 1 ]
then
local old_fpm_config_dir="/etc/php/$phpversion/fpm"
# If a config file exist in the common pool, move it.
if [ -e "$old_fpm_config_dir/pool.d/$app.conf" ]
then
ynh_print_info --message="Migrate to a dedicated php-fpm service for $app."
# Create a backup of the old file before migration
ynh_backup_if_checksum_is_different --file="$old_fpm_config_dir/pool.d/$app.conf"
# Remove the old php config file
ynh_secure_remove --file="$old_fpm_config_dir/pool.d/$app.conf"
# Reload php to release the socket and allow the dedicated service to use it
ynh_systemd_action --service_name=php${phpversion}-fpm --action=reload
fi
fi
ynh_backup_if_checksum_is_different --file="$finalphpconf"
if [ $use_template -eq 1 ]
then
# Usage 1, use the template in ../conf/php-fpm.conf
cp ../conf/php-fpm.conf "$finalphpconf"
ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$finalphpconf"
ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$finalphpconf"
ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$finalphpconf"
ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$phpversion" --target_file="$finalphpconf"
else
# Usage 2, generate a php-fpm config file with ynh_get_scalable_phpfpm
# Store settings
ynh_app_setting_set --app=$app --key=fpm_footprint --value=$footprint
ynh_app_setting_set --app=$app --key=fpm_usage --value=$usage
# Define the values to use for the configuration of php.
ynh_get_scalable_phpfpm --usage=$usage --footprint=$footprint
# Copy the default file
cp "/etc/php/$phpversion/fpm/pool.d/www.conf" "$finalphpconf"
# Replace standard variables into the default file
ynh_replace_string --match_string="^\[www\]" --replace_string="[$app]" --target_file="$finalphpconf"
ynh_replace_string --match_string=".*listen = .*" --replace_string="listen = /var/run/php/php$phpversion-fpm-$app.sock" --target_file="$finalphpconf"
ynh_replace_string --match_string="^user = .*" --replace_string="user = $app" --target_file="$finalphpconf"
ynh_replace_string --match_string="^group = .*" --replace_string="group = $app" --target_file="$finalphpconf"
ynh_replace_string --match_string=".*chdir = .*" --replace_string="chdir = $final_path" --target_file="$finalphpconf"
# Configure fpm children
ynh_replace_string --match_string=".*pm = .*" --replace_string="pm = $php_pm" --target_file="$finalphpconf"
ynh_replace_string --match_string=".*pm.max_children = .*" --replace_string="pm.max_children = $php_max_children" --target_file="$finalphpconf"
ynh_replace_string --match_string=".*pm.max_requests = .*" --replace_string="pm.max_requests = 500" --target_file="$finalphpconf"
ynh_replace_string --match_string=".*request_terminate_timeout = .*" --replace_string="request_terminate_timeout = 1d" --target_file="$finalphpconf"
if [ "$php_pm" = "dynamic" ]
then
ynh_replace_string --match_string=".*pm.start_servers = .*" --replace_string="pm.start_servers = $php_start_servers" --target_file="$finalphpconf"
ynh_replace_string --match_string=".*pm.min_spare_servers = .*" --replace_string="pm.min_spare_servers = $php_min_spare_servers" --target_file="$finalphpconf"
ynh_replace_string --match_string=".*pm.max_spare_servers = .*" --replace_string="pm.max_spare_servers = $php_max_spare_servers" --target_file="$finalphpconf"
elif [ "$php_pm" = "ondemand" ]
then
ynh_replace_string --match_string=".*pm.process_idle_timeout = .*" --replace_string="pm.process_idle_timeout = 10s" --target_file="$finalphpconf"
fi
# Comment unused parameters
if [ "$php_pm" != "dynamic" ]
then
ynh_replace_string --match_string=".*\(pm.start_servers = .*\)" --replace_string=";\1" --target_file="$finalphpconf"
ynh_replace_string --match_string=".*\(pm.min_spare_servers = .*\)" --replace_string=";\1" --target_file="$finalphpconf"
ynh_replace_string --match_string=".*\(pm.max_spare_servers = .*\)" --replace_string=";\1" --target_file="$finalphpconf"
fi
if [ "$php_pm" != "ondemand" ]
then
ynh_replace_string --match_string=".*\(pm.process_idle_timeout = .*\)" --replace_string=";\1" --target_file="$finalphpconf"
fi
# Concatene the extra config.
if [ -e ../conf/extra_php-fpm.conf ]; then
cat ../conf/extra_php-fpm.conf >> "$finalphpconf"
fi
fi
chown root: "$finalphpconf"
ynh_store_file_checksum --file="$finalphpconf"
if [ -e "../conf/php-fpm.ini" ]
then
echo "Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead." >&2
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"
ynh_backup_if_checksum_is_different "$finalphpini"
cp ../conf/php-fpm.ini "$finalphpini"
chown root: "$finalphpini"
ynh_store_file_checksum "$finalphpini"
fi
if [ $dedicated_service -eq 1 ]
then
# Create a dedicated php-fpm.conf for the service
local globalphpconf=$fpm_config_dir/php-fpm-$app.conf
cp /etc/php/${phpversion}/fpm/php-fpm.conf $globalphpconf
ynh_replace_string --match_string="^[; ]*pid *=.*" --replace_string="pid = /run/php/php${phpversion}-fpm-$app.pid" --target_file="$globalphpconf"
ynh_replace_string --match_string="^[; ]*error_log *=.*" --replace_string="error_log = /var/log/php/fpm-php.$app.log" --target_file="$globalphpconf"
ynh_replace_string --match_string="^[; ]*syslog.ident *=.*" --replace_string="syslog.ident = php-fpm-$app" --target_file="$globalphpconf"
ynh_replace_string --match_string="^[; ]*include *=.*" --replace_string="include = $finalphpconf" --target_file="$globalphpconf"
# Create a config for a dedicated php-fpm service for the app
echo "[Unit]
Description=PHP $phpversion FastCGI Process Manager for $app
After=network.target
[Service]
Type=notify
PIDFile=/run/php/php${phpversion}-fpm-$app.pid
ExecStart=/usr/sbin/php-fpm$phpversion --nodaemonize --fpm-config $globalphpconf
ExecReload=/bin/kill -USR2 \$MAINPID
[Install]
WantedBy=multi-user.target
" > ../conf/$fpm_service
# Create this dedicated php-fpm service
ynh_add_systemd_config --service=$fpm_service --template=$fpm_service
# Integrate the service in YunoHost admin panel
yunohost service add $fpm_service --log /var/log/php/fpm-php.$app.log --log_type file --description "Php-fpm dedicated to $app"
# Configure log rotate
ynh_use_logrotate --logfile=/var/log/php
# Restart the service, as this service is either stopped or only for this app
ynh_systemd_action --service_name=$fpm_service --action=restart
else
# Reload php, to not impact other parts of the system using php
ynh_systemd_action --service_name=$fpm_service --action=reload
fi
}
# Remove the dedicated php-fpm config
@ -56,12 +265,280 @@ ynh_add_fpm_config () {
ynh_remove_fpm_config () {
local fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir)
local fpm_service=$(ynh_app_setting_get --app=$app --key=fpm_service)
# Assume php version 7 if not set
local dedicated_service=$(ynh_app_setting_get --app=$app --key=fpm_dedicated_service)
dedicated_service=${dedicated_service:-0}
# Get the version of php used by this app
local phpversion=$(ynh_app_setting_get $app phpversion)
# Assume default PHP-FPM version by default
phpversion="${phpversion:-$YNH_DEFAULT_PHP_VERSION}"
# Assume default php files if not set
if [ -z "$fpm_config_dir" ]; then
fpm_config_dir="/etc/php/7.0/fpm"
fpm_service="php7.0-fpm"
fpm_config_dir="/etc/php/$YNH_DEFAULT_PHP_VERSION/fpm"
fpm_service="php$YNH_DEFAULT_PHP_VERSION-fpm"
fi
ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf"
ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" 2>&1
if [ $dedicated_service -eq 1 ]
then
# Remove the dedicated service php-fpm service for the app
ynh_remove_systemd_config --service=$fpm_service
# Remove the global php-fpm conf
ynh_secure_remove --file="$fpm_config_dir/php-fpm-$app.conf"
# Remove the service from the list of services known by Yunohost
yunohost service remove $fpm_service
elif ynh_package_is_installed --package="php${phpversion}-fpm"; then
ynh_systemd_action --service_name=$fpm_service --action=reload
fi
ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf"
ynh_exec_warn_less ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini"
# If the php version used is not the default version for YunoHost
if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ]
then
# Remove this specific version of php
ynh_remove_php
fi
}
# Install another version of php.
#
# [internal]
#
# usage: ynh_install_php --phpversion=phpversion [--package=packages]
# | arg: -v, --phpversion - Version of php to install.
# | arg: -p, --package - Additionnal php packages to install
ynh_install_php () {
# Declare an array to define the options of this helper.
local legacy_args=vp
declare -Ar args_array=( [v]=phpversion= [p]=package= )
local phpversion
local package
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
package=${package:-}
# Store phpversion into the config of this app
ynh_app_setting_set $app phpversion $phpversion
if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ]
then
ynh_die "Do not use ynh_install_php to install php$YNH_DEFAULT_PHP_VERSION"
fi
# Create the file if doesn't exist already
touch /etc/php/ynh_app_version
# Do not add twice the same line
if ! grep --quiet "$YNH_APP_INSTANCE_NAME:" "/etc/php/ynh_app_version"
then
# Store the ID of this app and the version of php requested for it
echo "$YNH_APP_INSTANCE_NAME:$phpversion" | tee --append "/etc/php/ynh_app_version"
fi
# Add an extra repository for those packages
ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(lsb_release -sc) main" --key="https://packages.sury.org/php/apt.gpg" --priority=995 --name=extra_php_version
# Install requested dependencies from this extra repository.
# Install php-fpm first, otherwise php will install apache as a dependency.
ynh_add_app_dependencies --package="php${phpversion}-fpm"
ynh_add_app_dependencies --package="php$phpversion php${phpversion}-common $package"
# Set the default php version back as the default version for php-cli.
update-alternatives --set php /usr/bin/php$YNH_DEFAULT_PHP_VERSION
# Pin this extra repository after packages are installed to prevent sury of doing shit
ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version
ynh_pin_repo --package="php${YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append
# Advertise service in admin panel
yunohost service add php${phpversion}-fpm --log "/var/log/php${phpversion}-fpm.log"
}
# Remove the specific version of php used by the app.
#
# [internal]
#
# usage: ynh_install_php
ynh_remove_php () {
# Get the version of php used by this app
local phpversion=$(ynh_app_setting_get $app phpversion)
if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ] || [ -z "$phpversion" ]
then
if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ]
then
ynh_print_err "Do not use ynh_remove_php to remove php$YNH_DEFAULT_PHP_VERSION !"
fi
return 0
fi
# Create the file if doesn't exist already
touch /etc/php/ynh_app_version
# Remove the line for this app
sed --in-place "/$YNH_APP_INSTANCE_NAME:$phpversion/d" "/etc/php/ynh_app_version"
# If no other app uses this version of php, remove it.
if ! grep --quiet "$phpversion" "/etc/php/ynh_app_version"
then
# Remove the service from the admin panel
if ynh_package_is_installed --package="php${phpversion}-fpm"; then
yunohost service remove php${phpversion}-fpm
fi
# Purge php dependencies for this version.
ynh_package_autopurge "php$phpversion php${phpversion}-fpm php${phpversion}-common"
fi
}
# Define the values to configure php-fpm
#
# [internal]
#
# usage: ynh_get_scalable_phpfpm --usage=usage --footprint=footprint [--print]
# | arg: -f, --footprint - Memory footprint of the service (low/medium/high).
# low - Less than 20Mb of ram by pool.
# medium - Between 20Mb and 40Mb of ram by pool.
# high - More than 40Mb of ram by pool.
# Or specify exactly the footprint, the load of the service as Mb by pool instead of having a standard value.
# To have this value, use the following command and stress the service.
# watch -n0.5 ps -o user,cmd,%cpu,rss -u APP
#
# | arg: -u, --usage - Expected usage of the service (low/medium/high).
# low - Personal usage, behind the sso.
# medium - Low usage, few people or/and publicly accessible.
# high - High usage, frequently visited website.
#
# | arg: -p, --print - Print the result (intended for debug purpose only when packaging the app)
ynh_get_scalable_phpfpm () {
local legacy_args=ufp
# Declare an array to define the options of this helper.
declare -Ar args_array=( [u]=usage= [f]=footprint= [p]=print )
local usage
local footprint
local print
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
# Set all characters as lowercase
footprint=${footprint,,}
usage=${usage,,}
print=${print:-0}
if [ "$footprint" = "low" ]
then
footprint=20
elif [ "$footprint" = "medium" ]
then
footprint=35
elif [ "$footprint" = "high" ]
then
footprint=50
fi
# Define the factor to determine min_spare_servers
# to avoid having too few children ready to start for heavy apps
if [ $footprint -le 20 ]
then
min_spare_servers_factor=8
elif [ $footprint -le 35 ]
then
min_spare_servers_factor=5
else
min_spare_servers_factor=3
fi
# Define the way the process manager handle child processes.
if [ "$usage" = "low" ]
then
php_pm=ondemand
elif [ "$usage" = "medium" ]
then
php_pm=dynamic
elif [ "$usage" = "high" ]
then
php_pm=static
else
ynh_die --message="Does not recognize '$usage' as an usage value."
fi
# Get the total of RAM available, except swap.
local max_ram=$(ynh_get_ram --total --ignore_swap)
at_least_one() {
# Do not allow value below 1
if [ $1 -le 0 ]
then
echo 1
else
echo $1
fi
}
# Define pm.max_children
# The value of pm.max_children is the total amount of ram divide by 2 and divide again by the footprint of a pool for this app.
# So if php-fpm start the maximum of children, it won't exceed half of the ram.
php_max_children=$(( $max_ram / 2 / $footprint ))
# If process manager is set as static, use half less children.
# Used as static, there's always as many children as the value of pm.max_children
if [ "$php_pm" = "static" ]
then
php_max_children=$(( $php_max_children / 2 ))
fi
php_max_children=$(at_least_one $php_max_children)
# To not overload the proc, limit the number of children to 4 times the number of cores.
local core_number=$(nproc)
local max_proc=$(( $core_number * 4 ))
if [ $php_max_children -gt $max_proc ]
then
php_max_children=$max_proc
fi
# Get a potential forced value for php_max_children
local php_forced_max_children=$(ynh_app_setting_get --app=$app --key=php_forced_max_children)
if [ -n "$php_forced_max_children" ]; then
php_max_children=$php_forced_max_children
fi
if [ "$php_pm" = "dynamic" ]
then
# Define pm.start_servers, pm.min_spare_servers and pm.max_spare_servers for a dynamic process manager
php_min_spare_servers=$(( $php_max_children / $min_spare_servers_factor ))
php_min_spare_servers=$(at_least_one $php_min_spare_servers)
php_max_spare_servers=$(( $php_max_children / 2 ))
php_max_spare_servers=$(at_least_one $php_max_spare_servers)
php_start_servers=$(( $php_min_spare_servers + ( $php_max_spare_servers - $php_min_spare_servers ) /2 ))
php_start_servers=$(at_least_one $php_start_servers)
else
php_min_spare_servers=0
php_max_spare_servers=0
php_start_servers=0
fi
if [ $print -eq 1 ]
then
ynh_debug --message="Footprint=${footprint}Mb by pool."
ynh_debug --message="Process manager=$php_pm"
ynh_debug --message="Max RAM=${max_ram}Mb"
if [ "$php_pm" != "static" ]; then
ynh_debug --message="\nMax estimated footprint=$(( $php_max_children * $footprint ))"
ynh_debug --message="Min estimated footprint=$(( $php_min_spare_servers * $footprint ))"
fi
if [ "$php_pm" = "dynamic" ]; then
ynh_debug --message="Estimated average footprint=$(( $php_max_spare_servers * $footprint ))"
elif [ "$php_pm" = "static" ]; then
ynh_debug --message="Estimated footprint=$(( $php_max_children * $footprint ))"
fi
ynh_debug --message="\nRaw php-fpm values:"
ynh_debug --message="pm.max_children = $php_max_children"
if [ "$php_pm" = "dynamic" ]; then
ynh_debug --message="pm.start_servers = $php_start_servers"
ynh_debug --message="pm.min_spare_servers = $php_min_spare_servers"
ynh_debug --message="pm.max_spare_servers = $php_max_spare_servers"
fi
fi
}

View file

@ -105,7 +105,7 @@ EOF
if [[ "$1" == "set" ]] && [[ "${4:-}" == "/" ]]
then
ynh_permission_update --permission "main" --add "visitors"
elif [[ "$1" == "delete" ]] && [[ "${current_value:-}" == "/" ]]
elif [[ "$1" == "delete" ]] && [[ "${current_value:-}" == "/" ]] && [[ -n "$(ynh_app_setting_get --app=$2 --key='is_public' )" ]]
then
ynh_permission_update --permission "main" --remove "visitors"
fi
@ -178,6 +178,8 @@ ynh_webpath_register () {
#
# Requires YunoHost version 3.7.0 or higher.
ynh_permission_create() {
# Declare an array to define the options of this helper.
local legacy_args=pua
declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= )
local permission
local url
@ -206,6 +208,8 @@ ynh_permission_create() {
#
# Requires YunoHost version 3.7.0 or higher.
ynh_permission_delete() {
# Declare an array to define the options of this helper.
local legacy_args=p
declare -Ar args_array=( [p]=permission= )
local permission
ynh_handle_getopts_args "$@"
@ -220,6 +224,8 @@ ynh_permission_delete() {
#
# Requires YunoHost version 3.7.0 or higher.
ynh_permission_exists() {
# Declare an array to define the options of this helper.
local legacy_args=p
declare -Ar args_array=( [p]=permission= )
local permission
ynh_handle_getopts_args "$@"
@ -235,6 +241,8 @@ ynh_permission_exists() {
#
# Requires YunoHost version 3.7.0 or higher.
ynh_permission_url() {
# Declare an array to define the options of this helper.
local legacy_args=pu
declare -Ar args_array=([p]=permission= [u]=url=)
local permission
local url
@ -260,6 +268,8 @@ ynh_permission_url() {
# example: ynh_permission_update --permission admin --add samdoe --remove all_users
# Requires YunoHost version 3.7.0 or higher.
ynh_permission_update() {
# Declare an array to define the options of this helper.
local legacy_args=par
declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= )
local permission
local add
@ -275,3 +285,29 @@ ynh_permission_update() {
yunohost user permission update "$app.$permission" ${add:-} ${remove:-}
}
# Check if a permission exists
#
# usage: ynh_permission_has_user --permission=permission --user=user
# | arg: -p, --permission - the permission to check
# | arg: -u, --user - the user seek in the permission
#
# example: ynh_permission_has_user --permission=main --user=visitors
#
# Requires YunoHost version 3.7.1 or higher.
ynh_permission_has_user() {
local legacy_args=pu
# Declare an array to define the options of this helper.
declare -Ar args_array=( [p]=permission= [u]=user= )
local permission
local user
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
if ! ynh_permission_exists --permission=$permission
then
return 1
fi
yunohost user permission info "$app.$permission" | grep -w -q "$user"
}

View file

@ -238,8 +238,13 @@ ynh_local_curl () {
# Wait untils nginx has fully reloaded (avoid curl fail with http2)
sleep 2
local cookiefile=/tmp/ynh-$app-cookie.txt
touch $cookiefile
chown root $cookiefile
chmod 700 $cookiefile
# Curl the URL
curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" --cookie-jar /tmp/ynh-$app-cookie.txt --cookie /tmp/ynh-$app-cookie.txt
curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" --cookie-jar $cookiefile --cookie $cookiefile
}
# Render templates with Jinja2

View file

@ -63,6 +63,9 @@ do_pre_regen() {
cp -a ldap.conf slapd.conf "$ldap_dir"
cp -a sudo.schema mailserver.schema yunohost.schema "$schema_dir"
mkdir -p ${pending_dir}/etc/systemd/system/slapd.service.d/
cp systemd-override.conf ${pending_dir}/etc/systemd/system/slapd.service.d/ynh-override.conf
install -D -m 644 slapd.default "${pending_dir}/etc/default/slapd"
}
@ -83,6 +86,13 @@ do_post_regen() {
chmod o-rwx /etc/yunohost/certs/yunohost.org/
chmod -R g+rx /etc/yunohost/certs/yunohost.org/
# If we changed the systemd ynh-override conf
if echo "$regen_conf_files" | sed 's/,/\n/g' | grep -q "^/etc/systemd/system/slapd.service.d/ynh-override.conf$"
then
systemctl daemon-reload
systemctl restart slapd
fi
[ -z "$regen_conf_files" ] && exit 0
# check the slapd config file at first

View file

@ -54,7 +54,6 @@ do_post_regen() {
chown -R metronome:www-data "/var/xmpp-upload/${domain}"
done
# fix some permissions
chown -R metronome: /var/lib/metronome/
chown -R metronome: /etc/metronome/conf.d/

View file

@ -23,6 +23,7 @@ do_init_regen() {
rm -f "${nginx_dir}/sites-enabled/default"
export compatibility="intermediate"
ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc"
ynh_render_template "yunohost_admin.conf" "${nginx_conf_dir}/yunohost_admin.conf"
# Restart nginx if conf looks good, otherwise display error and exit unhappy
@ -110,6 +111,21 @@ do_post_regen() {
mkdir -p "/etc/nginx/conf.d/${domain}.d"
done
# Get rid of legacy lets encrypt snippets
for domain in $domain_list; do
# If the legacy letsencrypt / acme-challenge domain-specific snippet is still there
if [ -e /etc/nginx/conf.d/${domain}.d/000-acmechallenge.conf ]
then
# And if we're effectively including the new domain-independant snippet now
if grep -q "include /etc/nginx/conf.d/acme-challenge.conf.inc;" /etc/nginx/conf.d/${domain}.conf
then
# Delete the old domain-specific snippet
rm /etc/nginx/conf.d/${domain}.d/000-acmechallenge.conf
fi
fi
done
# Reload nginx configuration
pgrep nginx && service nginx reload
}

View file

@ -35,7 +35,8 @@ do_pre_regen() {
> "${default_dir}/postsrsd"
# adapt it for IPv4-only hosts
if [ ! -f /proc/net/if_inet6 ]; then
ipv6="$(yunohost settings get 'smtp.allow_ipv6')"
if [ "$ipv6" == "False" ] || [ ! -f /proc/net/if_inet6 ]; then
sed -i \
's/ \[::ffff:127.0.0.0\]\/104 \[::1\]\/128//g' \
"${postfix_dir}/main.cf"

View file

@ -50,6 +50,21 @@ do_pre_regen() {
do_post_regen() {
regen_conf_files=$1
# Fuck it, those domain/search entries from dhclient are usually annoying
# lying shit from the ISP trying to MiTM
if grep -q -E "^ *(domain|search)" /run/resolvconf/resolv.conf
then
if grep -q -E "^ *(domain|search)" /run/resolvconf/interface/*.dhclient 2>/dev/null
then
sed -E "s/^(domain|search)/#\1/g" -i /run/resolvconf/interface/*.dhclient
fi
grep -q '^supersede domain-name "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo 'supersede domain-name "";' >> /etc/dhcp/dhclient.conf
grep -q '^supersede domain-search "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo 'supersede domain-search "";' >> /etc/dhcp/dhclient.conf
grep -q '^supersede name "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo 'supersede name "";' >> /etc/dhcp/dhclient.conf
systemctl restart resolvconf
fi
[[ -z "$regen_conf_files" ]] \
|| service dnsmasq restart
}

View file

@ -11,62 +11,66 @@ from yunohost.utils.packages import ynh_packages_version
class BaseSystemDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 3600 * 24
cache_duration = 600
dependencies = []
def run(self):
# Detect virt technology (if not bare metal) and arch
# Also possibly the board name
virt = check_output("systemd-detect-virt").strip() or "bare-metal"
# Gotta have this "|| true" because it systemd-detect-virt return 'none'
# with an error code on bare metal ~.~
virt = check_output("systemd-detect-virt || true", shell=True).strip()
if virt.lower() == "none":
virt = "bare-metal"
# Detect arch
arch = check_output("dpkg --print-architecture").strip()
hardware = dict(meta={"test": "hardware"},
status="INFO",
data={"virt": virt, "arch": arch},
summary=("diagnosis_basesystem_hardware", {"virt": virt, "arch": arch}))
summary="diagnosis_basesystem_hardware")
# Also possibly the board name
if os.path.exists("/proc/device-tree/model"):
model = read_file('/proc/device-tree/model').strip()
hardware["data"]["board"] = model
hardware["details"] = [("diagnosis_basesystem_hardware_board", (model,))]
hardware["data"]["model"] = model
hardware["details"] = ["diagnosis_basesystem_hardware_board"]
yield hardware
# Kernel version
kernel_version = read_file('/proc/sys/kernel/osrelease').strip()
yield dict(meta={"test": "kernel"},
data={"kernel_version": kernel_version},
status="INFO",
summary=("diagnosis_basesystem_kernel", {"kernel_version": kernel_version}))
summary="diagnosis_basesystem_kernel")
# Debian release
debian_version = read_file("/etc/debian_version").strip()
yield dict(meta={"test": "host"},
data={"debian_version": debian_version},
status="INFO",
summary=("diagnosis_basesystem_host", {"debian_version": debian_version}))
summary="diagnosis_basesystem_host")
# Yunohost packages versions
ynh_packages = ynh_packages_version()
# We check if versions are consistent (e.g. all 3.6 and not 3 packages with 3.6 and the other with 3.5)
# This is a classical issue for upgrades that failed in the middle
# (or people upgrading half of the package because they did 'apt upgrade' instead of 'dist-upgrade')
# Here, ynh_core_version is for example "3.5.4.12", so [:3] is "3.5" and we check it's the same for all packages
ynh_packages = ynh_packages_version()
ynh_core_version = ynh_packages["yunohost"]["version"]
consistent_versions = all(infos["version"][:3] == ynh_core_version[:3] for infos in ynh_packages.values())
ynh_version_details = [("diagnosis_basesystem_ynh_single_version", (package, infos["version"], infos["repo"]))
ynh_version_details = [("diagnosis_basesystem_ynh_single_version",
{"package":package,
"version": infos["version"],
"repo": infos["repo"]}
)
for package, infos in ynh_packages.items()]
if consistent_versions:
yield dict(meta={"test": "ynh_versions"},
data={"main_version": ynh_core_version, "repo": ynh_packages["yunohost"]["repo"]},
status="INFO",
summary=("diagnosis_basesystem_ynh_main_version",
{"main_version": ynh_core_version,
"repo": ynh_packages["yunohost"]["repo"]}),
details=ynh_version_details)
else:
yield dict(meta={"test": "ynh_versions"},
data={"main_version": ynh_core_version, "repo": ynh_packages["yunohost"]["repo"]},
status="ERROR",
summary=("diagnosis_basesystem_ynh_inconsistent_versions", {}),
status="INFO" if consistent_versions else "ERROR",
summary="diagnosis_basesystem_ynh_main_version" if consistent_versions else "diagnosis_basesystem_ynh_inconsistent_versions",
details=ynh_version_details)

View file

@ -8,12 +8,12 @@ from moulinette.utils.process import check_output
from moulinette.utils.filesystem import read_file
from yunohost.diagnosis import Diagnoser
from yunohost.utils.network import get_network_interfaces
class IPDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 60
cache_duration = 600
dependencies = []
def run(self):
@ -28,7 +28,7 @@ class IPDiagnoser(Diagnoser):
if not can_ping_ipv4 and not can_ping_ipv6:
yield dict(meta={"test": "ping"},
status="ERROR",
summary=("diagnosis_ip_not_connected_at_all", {}))
summary="diagnosis_ip_not_connected_at_all")
# Not much else we can do if there's no internet at all
return
@ -41,7 +41,7 @@ class IPDiagnoser(Diagnoser):
# In every case, we can check that resolvconf seems to be okay
# (symlink managed by resolvconf service + pointing to dnsmasq)
good_resolvconf = self.resolvconf_is_symlink() and self.resolvconf_points_to_localhost()
good_resolvconf = self.good_resolvconf()
# If we can't resolve domain names at all, that's a pretty big issue ...
# If it turns out that at the same time, resolvconf is bad, that's probably
@ -49,20 +49,19 @@ class IPDiagnoser(Diagnoser):
if not can_resolve_dns:
yield dict(meta={"test": "dnsresolv"},
status="ERROR",
summary=("diagnosis_ip_broken_dnsresolution", {}) if good_resolvconf
else ("diagnosis_ip_broken_resolvconf", {}))
summary="diagnosis_ip_broken_dnsresolution" if good_resolvconf else "diagnosis_ip_broken_resolvconf")
return
# Otherwise, if the resolv conf is bad but we were able to resolve domain name,
# still warn that we're using a weird resolv conf ...
elif not good_resolvconf:
yield dict(meta={"test": "dnsresolv"},
status="WARNING",
summary=("diagnosis_ip_weird_resolvconf", {}),
details=[("diagnosis_ip_weird_resolvconf_details", ())])
summary="diagnosis_ip_weird_resolvconf",
details=["diagnosis_ip_weird_resolvconf_details"])
else:
yield dict(meta={"test": "dnsresolv"},
status="SUCCESS",
summary=("diagnosis_ip_dnsresolution_working", {}))
summary="diagnosis_ip_dnsresolution_working")
# ##################################################### #
# IP DIAGNOSIS : Check that we're actually able to talk #
@ -72,17 +71,28 @@ class IPDiagnoser(Diagnoser):
ipv4 = self.get_public_ip(4) if can_ping_ipv4 else None
ipv6 = self.get_public_ip(6) if can_ping_ipv6 else None
yield dict(meta={"test": "ip", "version": 4},
data=ipv4,
status="SUCCESS" if ipv4 else "ERROR",
summary=("diagnosis_ip_connected_ipv4", {}) if ipv4
else ("diagnosis_ip_no_ipv4", {}))
network_interfaces = get_network_interfaces()
def get_local_ip(version):
local_ip = {iface:addr[version].split("/")[0]
for iface, addr in network_interfaces.items() if version in addr}
if not local_ip:
return None
elif len(local_ip):
return next(iter(local_ip.values()))
else:
return local_ip
yield dict(meta={"test": "ip", "version": 6},
data=ipv6,
yield dict(meta={"test": "ipv4"},
data={"global": ipv4, "local": get_local_ip("ipv4")},
status="SUCCESS" if ipv4 else "ERROR",
summary="diagnosis_ip_connected_ipv4" if ipv4 else "diagnosis_ip_no_ipv4",
details=["diagnosis_ip_global", "diagnosis_ip_local"] if ipv4 else None)
yield dict(meta={"test": "ipv6"},
data={"global": ipv6, "local": get_local_ip("ipv6")},
status="SUCCESS" if ipv6 else "WARNING",
summary=("diagnosis_ip_connected_ipv6", {}) if ipv6
else ("diagnosis_ip_no_ipv6", {}))
summary="diagnosis_ip_connected_ipv6" if ipv6 else "diagnosis_ip_no_ipv6",
details=["diagnosis_ip_global", "diagnosis_ip_local"] if ipv6 else None)
# TODO / FIXME : add some attempt to detect ISP (using whois ?) ?
@ -96,7 +106,7 @@ class IPDiagnoser(Diagnoser):
# If we are indeed connected in ipv4 or ipv6, we should find a default route
routes = check_output("ip -%s route" % protocol).split("\n")
if not [r for r in routes if r.startswith("default")]:
if not any(r.startswith("default") for r in routes):
return False
# We use the resolver file as a list of well-known, trustable (ie not google ;)) IPs that we can ping
@ -121,13 +131,12 @@ class IPDiagnoser(Diagnoser):
def can_resolve_dns(self):
return os.system("dig +short ip.yunohost.org >/dev/null 2>/dev/null") == 0
def resolvconf_is_symlink(self):
return os.path.realpath("/etc/resolv.conf") == "/run/resolvconf/resolv.conf"
def resolvconf_points_to_localhost(self):
file_ = "/etc/resolv.conf"
resolvers = [r.split(" ")[1] for r in read_file(file_).split("\n") if r.startswith("nameserver")]
return resolvers == ["127.0.0.1"]
def good_resolvconf(self):
content = read_file("/etc/resolv.conf").strip().split("\n")
# Ignore comments and empty lines
content = [l.strip() for l in content if l.strip() and not l.strip().startswith("#") and not l.strip().startswith("search")]
# We should only find a "nameserver 127.0.0.1"
return len(content) == 1 and content[0].split() == ["nameserver", "127.0.0.1"]
def get_public_ip(self, protocol=4):

View file

@ -2,9 +2,9 @@
import os
from moulinette.utils.process import check_output
from moulinette.utils.filesystem import read_file
from yunohost.utils.network import dig
from yunohost.diagnosis import Diagnoser
from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain
@ -12,7 +12,7 @@ from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain
class DNSRecordsDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 3600 * 24
cache_duration = 600
dependencies = ["ip"]
def run(self):
@ -38,11 +38,10 @@ class DNSRecordsDiagnoser(Diagnoser):
def check_domain(self, domain, is_main_domain, is_subdomain):
expected_configuration = _build_dns_conf(domain)
expected_configuration = _build_dns_conf(domain, include_empty_AAAA_if_no_ipv6=True)
# FIXME: Here if there are no AAAA record, we should add something to expect "no" AAAA record
# to properly diagnose situations where people have a AAAA record but no IPv6
categories = ["basic", "mail", "xmpp", "extra"]
# For subdomains, we only diagnosis A and AAAA records
if is_subdomain:
categories = ["basic"]
@ -50,44 +49,92 @@ class DNSRecordsDiagnoser(Diagnoser):
records = expected_configuration[category]
discrepancies = []
results = {}
for r in records:
current_value = self.get_current_record(domain, r["name"], r["type"]) or "None"
expected_value = r["value"] if r["value"] != "@" else domain + "."
id_ = r["type"] + ":" + r["name"]
r["current"] = self.get_current_record(domain, r["name"], r["type"])
if r["value"] == "@":
r["value"] = domain + "."
if current_value == "None":
discrepancies.append(("diagnosis_dns_missing_record", (r["type"], r["name"], expected_value)))
elif current_value != expected_value:
discrepancies.append(("diagnosis_dns_discrepancy", (r["type"], r["name"], expected_value, current_value)))
if self.current_record_match_expected(r):
results[id_] = "OK"
else:
if r["current"] is None:
results[id_] = "MISSING"
discrepancies.append(("diagnosis_dns_missing_record", r))
else:
results[id_] = "WRONG"
discrepancies.append(("diagnosis_dns_discrepancy", r))
def its_important():
# Every mail DNS records are important for main domain
# For other domain, we only report it as a warning for now...
if is_main_domain and category == "mail":
return True
elif category == "basic":
# A bad or missing A record is critical ...
# And so is a wrong AAAA record
# (However, a missing AAAA record is acceptable)
if results["A:@"] != "OK" or results["AAAA:@"] == "WRONG":
return True
return False
if discrepancies:
status = "ERROR" if (category == "basic" or (is_main_domain and category != "extra")) else "WARNING"
summary = ("diagnosis_dns_bad_conf", {"domain": domain, "category": category})
status = "ERROR" if its_important() else "WARNING"
summary = "diagnosis_dns_bad_conf"
else:
status = "SUCCESS"
summary = ("diagnosis_dns_good_conf", {"domain": domain, "category": category})
summary = "diagnosis_dns_good_conf"
output = dict(meta={"domain": domain, "category": category},
data=results,
status=status,
summary=summary)
if discrepancies:
output["details"] = discrepancies
output["details"] = ["diagnosis_dns_point_to_doc"] + discrepancies
yield output
def get_current_record(self, domain, name, type_):
if name == "@":
command = "dig +short @%s %s %s" % (self.resolver, type_, domain)
query = "%s.%s" % (name, domain) if name != "@" else domain
success, answers = dig(query, type_, resolvers="force_external")
if success != "ok":
return None
else:
command = "dig +short @%s %s %s.%s" % (self.resolver, type_, name, domain)
# FIXME : gotta handle case where this command fails ...
# e.g. no internet connectivity (dependency mechanism to good result from 'ip' diagosis ?)
# or the resolver is unavailable for some reason
output = check_output(command).strip()
if output.startswith('"') and output.endswith('"'):
output = '"' + ' '.join(output.replace('"', ' ').split()) + '"'
return output
return answers[0] if len(answers) == 1 else answers
def current_record_match_expected(self, r):
if r["value"] is not None and r["current"] is None:
return False
if r["value"] is None and r["current"] is not None:
return False
elif isinstance(r["current"], list):
return False
if r["type"] == "TXT":
# Split expected/current
# from "v=DKIM1; k=rsa; p=hugekey;"
# to a set like {'v=DKIM1', 'k=rsa', 'p=...'}
expected = set(r["value"].strip(';" ').replace(";", " ").split())
current = set(r["current"].strip(';" ').replace(";", " ").split())
# For SPF, ignore parts starting by ip4: or ip6:
if r["name"] == "@":
current = {part for part in current if not part.startswith("ip4:") and not part.startswith("ip6:")}
return expected == current
elif r["type"] == "MX":
# For MX, we want to ignore the priority
expected = r["value"].split()[-1]
current = r["current"].split()[-1]
return expected == current
else:
return r["current"] == r["value"]
def main(args, env, loggers):

View file

@ -1,7 +1,6 @@
#!/usr/bin/env python
import os
import requests
from yunohost.diagnosis import Diagnoser
from yunohost.utils.error import YunohostError
@ -10,11 +9,13 @@ from yunohost.service import _get_services
class PortsDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 3600
cache_duration = 600
dependencies = ["ip"]
def run(self):
# TODO: report a warning if port 53 or 5353 is exposed to the outside world...
# This dict is something like :
# { 80: "nginx",
# 25: "postfix",
@ -26,35 +27,89 @@ class PortsDiagnoser(Diagnoser):
for port in infos.get("needs_exposed_ports", []):
ports[port] = service
ipversions = []
ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {}
if ipv4.get("status") == "SUCCESS":
ipversions.append(4)
# To be discussed: we could also make this check dependent on the
# existence of an AAAA record...
ipv6 = Diagnoser.get_cached_report("ip", item={"test": "ipv6"}) or {}
if ipv6.get("status") == "SUCCESS":
ipversions.append(6)
# Fetch test result for each relevant IP version
results = {}
for ipversion in ipversions:
try:
r = requests.post('https://diagnosis.yunohost.org/check-ports', json={'ports': ports.keys()}, timeout=30)
if r.status_code not in [200, 400, 418]:
raise Exception("Bad response from the server https://diagnosis.yunohost.org/check-ports : %s - %s" % (str(r.status_code), r.content))
r = r.json()
if "status" not in r.keys():
raise Exception("Bad syntax for response ? Raw json: %s" % str(r))
elif r["status"] == "error":
if "content" in r.keys():
raise Exception(r["content"])
else:
raise Exception("Bad syntax for response ? Raw json: %s" % str(r))
elif r["status"] != "ok" or "ports" not in r.keys() or not isinstance(r["ports"], dict):
raise Exception("Bad syntax for response ? Raw json: %s" % str(r))
r = Diagnoser.remote_diagnosis('check-ports',
data={'ports': ports.keys()},
ipversion=ipversion)
results[ipversion] = r["ports"]
except Exception as e:
raise YunohostError("diagnosis_ports_could_not_diagnose", error=e)
yield dict(meta={"reason": "remote_diagnosis_failed", "ipversion": ipversion},
data={"error": str(e)},
status="WARNING",
summary="diagnosis_ports_could_not_diagnose",
details=["diagnosis_ports_could_not_diagnose_details"])
continue
ipversions = results.keys()
if not ipversions:
return
for port, service in sorted(ports.items()):
port = str(port)
category = services[service].get("category", "[?]")
if r["ports"].get(str(port), None) is not True:
yield dict(meta={"port": port, "needed_by": service},
status="ERROR",
summary=("diagnosis_ports_unreachable", {"port": port}),
details=[("diagnosis_ports_needed_by", (service, category)), ("diagnosis_ports_forwarding_tip", ())])
else:
yield dict(meta={"port": port, "needed_by": service},
# If both IPv4 and IPv6 (if applicable) are good
if all(results[ipversion].get(port) is True for ipversion in ipversions):
yield dict(meta={"port": port},
data={"service": service, "category": category},
status="SUCCESS",
summary=("diagnosis_ports_ok", {"port": port}),
details=[("diagnosis_ports_needed_by", (service, category))])
summary="diagnosis_ports_ok",
details=["diagnosis_ports_needed_by"])
# If both IPv4 and IPv6 (if applicable) are failed
elif all(results[ipversion].get(port) is not True for ipversion in ipversions):
yield dict(meta={"port": port},
data={"service": service, "category": category},
status="ERROR",
summary="diagnosis_ports_unreachable",
details=["diagnosis_ports_needed_by", "diagnosis_ports_forwarding_tip"])
# If only IPv4 is failed or only IPv6 is failed (if applicable)
else:
passed, failed = (4, 6) if results[4].get(port) is True else (6, 4)
# Failing in ipv4 is critical.
# If we failed in IPv6 but there's in fact no AAAA record
# It's an acceptable situation and we shall not report an
# error
# If any AAAA record is set, IPv6 is important...
def ipv6_is_important():
dnsrecords = Diagnoser.get_cached_report("dnsrecords") or {}
return any(record["data"]["AAAA:@"] in ["OK", "WRONG"] for record in dnsrecords.get("items", []))
if failed == 4 or ipv6_is_important():
yield dict(meta={"port": port},
data={"service": service, "category": category, "passed": passed, "failed": failed},
status="ERROR",
summary="diagnosis_ports_partially_unreachable",
details=["diagnosis_ports_needed_by", "diagnosis_ports_forwarding_tip"])
# So otherwise we report a success
# And in addition we report an info about the failure in IPv6
# *with a different meta* (important to avoid conflicts when
# fetching the other info...)
else:
yield dict(meta={"port": port},
data={"service": service, "category": category},
status="SUCCESS",
summary="diagnosis_ports_ok",
details=["diagnosis_ports_needed_by"])
yield dict(meta={"test": "ipv6", "port": port},
data={"service": service, "category": category, "passed": passed, "failed": failed},
status="INFO",
summary="diagnosis_ports_partially_unreachable",
details=["diagnosis_ports_needed_by", "diagnosis_ports_forwarding_tip"])
def main(args, env, loggers):

View file

@ -4,58 +4,162 @@ import os
import random
import requests
from moulinette.utils.filesystem import read_file
from yunohost.diagnosis import Diagnoser
from yunohost.domain import domain_list
from yunohost.utils.error import YunohostError
DIAGNOSIS_SERVER = "diagnosis.yunohost.org"
class WebDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 3600
cache_duration = 600
dependencies = ["ip"]
def run(self):
nonce_digits = "0123456789abcedf"
all_domains = domain_list()["domains"]
domains_to_check = []
for domain in all_domains:
nonce = ''.join(random.choice(nonce_digits) for i in range(16))
# If the diagnosis location ain't defined, can't do diagnosis,
# probably because nginx conf manually modified...
nginx_conf = "/etc/nginx/conf.d/%s.conf" % domain
if ".well-known/ynh-diagnosis/" not in read_file(nginx_conf):
yield dict(meta={"domain": domain},
status="WARNING",
summary="diagnosis_http_nginx_conf_not_up_to_date",
details=["diagnosis_http_nginx_conf_not_up_to_date_details"])
else:
domains_to_check.append(domain)
self.nonce = ''.join(random.choice("0123456789abcedf") for i in range(16))
os.system("rm -rf /tmp/.well-known/ynh-diagnosis/")
os.system("mkdir -p /tmp/.well-known/ynh-diagnosis/")
os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % nonce)
os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % self.nonce)
if not domains_to_check:
return
# To perform hairpinning test, we gotta make sure that port forwarding
# is working and therefore we'll do it only if at least one ipv4 domain
# works.
self.do_hairpinning_test = False
ipversions = []
ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {}
if ipv4.get("status") == "SUCCESS":
ipversions.append(4)
# To be discussed: we could also make this check dependent on the
# existence of an AAAA record...
ipv6 = Diagnoser.get_cached_report("ip", item={"test": "ipv6"}) or {}
if ipv6.get("status") == "SUCCESS":
ipversions.append(6)
for item in self.test_http(domains_to_check, ipversions):
yield item
# If at least one domain is correctly exposed to the outside,
# attempt to diagnose hairpinning situations. On network with
# hairpinning issues, the server may be correctly exposed on the
# outside, but from the outside, it will be as if the port forwarding
# was not configured... Hence, calling for example
# "curl --head the.global.ip" will simply timeout...
if self.do_hairpinning_test:
global_ipv4 = ipv4.get("data", {}).get("global", None)
if global_ipv4:
try:
r = requests.post('https://diagnosis.yunohost.org/check-http', json={'domain': domain, "nonce": nonce}, timeout=30)
if r.status_code not in [200, 400, 418]:
raise Exception("Bad response from the server https://diagnosis.yunohost.org/check-http : %s - %s" % (str(r.status_code), r.content))
r = r.json()
if "status" not in r.keys():
raise Exception("Bad syntax for response ? Raw json: %s" % str(r))
elif r["status"] == "error" and ("code" not in r.keys() or not r["code"].startswith("error_http_check_")):
if "content" in r.keys():
raise Exception(r["content"])
else:
raise Exception("Bad syntax for response ? Raw json: %s" % str(r))
except Exception as e:
raise YunohostError("diagnosis_http_could_not_diagnose", error=e)
requests.head("http://" + global_ipv4, timeout=5)
except requests.exceptions.Timeout:
yield dict(meta={"test": "hairpinning"},
status="WARNING",
summary="diagnosis_http_hairpinning_issue",
details=["diagnosis_http_hairpinning_issue_details"])
except:
# Well I dunno what to do if that's another exception
# type... That'll most probably *not* be an hairpinning
# issue but something else super weird ...
pass
if r["status"] == "ok":
def test_http(self, domains, ipversions):
results = {}
for ipversion in ipversions:
try:
r = Diagnoser.remote_diagnosis('check-http',
data={'domains': domains,
"nonce": self.nonce},
ipversion=ipversion)
results[ipversion] = r["http"]
except Exception as e:
yield dict(meta={"reason": "remote_diagnosis_failed", "ipversion": ipversion},
data={"error": str(e)},
status="WARNING",
summary="diagnosis_http_could_not_diagnose",
details=["diagnosis_http_could_not_diagnose_details"])
continue
ipversions = results.keys()
if not ipversions:
return
for domain in domains:
# If both IPv4 and IPv6 (if applicable) are good
if all(results[ipversion][domain]["status"] == "ok" for ipversion in ipversions):
if 4 in ipversions:
self.do_hairpinning_test = True
yield dict(meta={"domain": domain},
status="SUCCESS",
summary=("diagnosis_http_ok", {"domain": domain}))
else:
detail = r["code"].replace("error_http_check", "diagnosis_http") if "code" in r else "diagnosis_http_unknown_error"
summary="diagnosis_http_ok")
# If both IPv4 and IPv6 (if applicable) are failed
elif all(results[ipversion][domain]["status"] != "ok" for ipversion in ipversions):
detail = results[4 if 4 in ipversions else 6][domain]["status"]
yield dict(meta={"domain": domain},
status="ERROR",
summary=("diagnosis_http_unreachable", {"domain": domain}),
details=[(detail,())])
summary="diagnosis_http_unreachable",
details=[detail.replace("error_http_check", "diagnosis_http")])
# If only IPv4 is failed or only IPv6 is failed (if applicable)
else:
passed, failed = (4, 6) if results[4][domain]["status"] == "ok" else (6, 4)
detail = results[failed][domain]["status"]
# In there or idk where else ...
# try to diagnose hairpinning situation by crafting a request for the
# global ip (from within local network) and seeing if we're getting the right page ?
# Failing in ipv4 is critical.
# If we failed in IPv6 but there's in fact no AAAA record
# It's an acceptable situation and we shall not report an
# error
def ipv6_is_important_for_this_domain():
dnsrecords = Diagnoser.get_cached_report("dnsrecords", item={"domain": domain, "category": "basic"}) or {}
AAAA_status = dnsrecords.get("data", {}).get("AAAA:@")
return AAAA_status in ["OK", "WRONG"]
if failed == 4 or ipv6_is_important_for_this_domain():
yield dict(meta={"domain": domain},
data={"passed": passed, "failed": failed},
status="ERROR",
summary="diagnosis_http_partially_unreachable",
details=[detail.replace("error_http_check", "diagnosis_http")])
# So otherwise we report a success (note that this info is
# later used to know that ACME challenge is doable)
#
# And in addition we report an info about the failure in IPv6
# *with a different meta* (important to avoid conflicts when
# fetching the other info...)
else:
self.do_hairpinning_test = True
yield dict(meta={"domain": domain},
status="SUCCESS",
summary="diagnosis_http_ok")
yield dict(meta={"test": "ipv6", "domain": domain},
data={"passed": passed, "failed": failed},
status="INFO",
summary="diagnosis_http_partially_unreachable",
details=[detail.replace("error_http_check", "diagnosis_http")])
def main(args, env, loggers):

View file

@ -1,42 +1,238 @@
#!/usr/bin/env python
import os
import dns.resolver
import socket
import re
from subprocess import CalledProcessError
from moulinette.utils.process import check_output
from moulinette.utils.filesystem import read_yaml
from yunohost.diagnosis import Diagnoser
from yunohost.domain import _get_maindomain, domain_list
from yunohost.settings import settings_get
from yunohost.utils.network import dig
DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/other/dnsbl_list.yml"
class MailDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 3600
cache_duration = 600
dependencies = ["ip"]
def run(self):
# Is outgoing port 25 filtered somehow ?
if os.system('/bin/nc -z -w2 yunohost.org 25') == 0:
yield dict(meta={"test": "ougoing_port_25"},
self.ehlo_domain = _get_maindomain()
self.mail_domains = domain_list()["domains"]
self.ipversions, self.ips = self.get_ips_checked()
# TODO Is a A/AAAA and MX Record ?
# TODO Are outgoing public IPs authorized to send mail by SPF ?
# TODO Validate DKIM and dmarc ?
# TODO check that the recent mail logs are not filled with thousand of email sending (unusual number of mail sent)
# TODO check for unusual failed sending attempt being refused in the logs ?
checks = ["check_outgoing_port_25", "check_ehlo", "check_fcrdns",
"check_blacklist", "check_queue"]
for check in checks:
self.logger_debug("Running " + check)
reports = list(getattr(self, check)())
for report in reports:
yield report
if not reports:
name = check[6:]
yield dict(meta={"test": "mail_" + name},
status="SUCCESS",
summary=("diagnosis_mail_ougoing_port_25_ok",{}))
else:
yield dict(meta={"test": "outgoing_port_25"},
summary="diagnosis_mail_" + name + "_ok")
def check_outgoing_port_25(self):
"""
Check outgoing port 25 is open and not blocked by router
This check is ran on IPs we could used to send mail.
"""
for ipversion in self.ipversions:
cmd = '/bin/nc -{ipversion} -z -w2 yunohost.org 25'.format(ipversion=ipversion)
if os.system(cmd) != 0:
yield dict(meta={"test": "outgoing_port_25", "ipversion": ipversion},
data={},
status="ERROR",
summary=("diagnosis_mail_ougoing_port_25_blocked",{}))
summary="diagnosis_mail_outgoing_port_25_blocked",
details=["diagnosis_mail_outgoing_port_25_blocked_details",
"diagnosis_mail_outgoing_port_25_blocked_relay_vpn"])
def check_ehlo(self):
"""
Check the server is reachable from outside and it's the good one
This check is ran on IPs we could used to send mail.
"""
# Mail blacklist using dig requests (c.f. ljf's code)
for ipversion in self.ipversions:
try:
r = Diagnoser.remote_diagnosis('check-smtp',
data={},
ipversion=ipversion)
except Exception as e:
yield dict(meta={"test": "mail_ehlo", "reason": "remote_server_failed",
"ipversion": ipversion},
data={"error": str(e)},
status="WARNING",
summary="diagnosis_mail_ehlo_could_not_diagnose",
details=["diagnosis_mail_ehlo_could_not_diagnose_details"])
continue
# SMTP reachability (c.f. check-smtp to be implemented on yunohost's remote diagnoser)
if r["status"] != "ok":
summary = r["status"].replace("error_smtp_", "diagnosis_mail_ehlo_")
yield dict(meta={"test": "mail_ehlo", "ipversion": ipversion},
data={},
status="ERROR",
summary=summary,
details=[summary + "_details"])
elif r["helo"] != self.ehlo_domain:
yield dict(meta={"test": "mail_ehlo", "ipversion": ipversion},
data={"wrong_ehlo": r["helo"], "right_ehlo": self.ehlo_domain},
status="ERROR",
summary="diagnosis_mail_ehlo_wrong",
details=["diagnosis_mail_ehlo_wrong_details"])
# ideally, SPF / DMARC / DKIM validation ... (c.f. https://github.com/alexAubin/yunoScripts/blob/master/yunoDKIM.py possibly though that looks horrible)
# check that the mail queue is not filled with hundreds of email pending
def check_fcrdns(self):
"""
Check the reverse DNS is well defined by doing a Forward-confirmed
reverse DNS check
This check is ran on IPs we could used to send mail.
"""
# check that the recent mail logs are not filled with thousand of email sending (unusual number of mail sent)
for ip in self.ips:
if ":" in ip:
ipversion = 6
details = ["diagnosis_mail_fcrdns_nok_details",
"diagnosis_mail_fcrdns_nok_alternatives_6"]
else:
ipversion = 4
details = ["diagnosis_mail_fcrdns_nok_details",
"diagnosis_mail_fcrdns_nok_alternatives_4"]
# check for unusual failed sending attempt being refused in the logs ?
try:
rdns_domain, _, _ = socket.gethostbyaddr(ip)
except socket.herror:
yield dict(meta={"test": "mail_fcrdns", "ipversion": ipversion},
data={"ip": ip, "ehlo_domain": self.ehlo_domain},
status="ERROR",
summary="diagnosis_mail_fcrdns_dns_missing",
details=details)
continue
if rdns_domain != self.ehlo_domain:
details = ["diagnosis_mail_fcrdns_different_from_ehlo_domain_details"] + details
yield dict(meta={"test": "mail_fcrdns", "ipversion": ipversion},
data={"ip": ip,
"ehlo_domain": self.ehlo_domain,
"rdns_domain": rdns_domain},
status="ERROR",
summary="diagnosis_mail_fcrdns_different_from_ehlo_domain",
details=details)
def check_blacklist(self):
"""
Check with dig onto blacklist DNS server
This check is ran on IPs and domains we could used to send mail.
"""
dns_blacklists = read_yaml(DEFAULT_DNS_BLACKLIST)
for item in self.ips + self.mail_domains:
for blacklist in dns_blacklists:
item_type = "domain"
if ":" in item:
item_type = 'ipv6'
elif re.match(r'^\d+\.\d+\.\d+\.\d+$', item):
item_type = 'ipv4'
if not blacklist[item_type]:
continue
# Build the query for DNSBL
subdomain = item
if item_type != "domain":
rev = dns.reversename.from_address(item)
subdomain = str(rev.split(3)[0])
query = subdomain + '.' + blacklist['dns_server']
# Do the DNS Query
status, _ = dig(query, 'A')
if status != 'ok':
continue
# Try to get the reason
details = []
status, answers = dig(query, 'TXT')
reason = "-"
if status == 'ok':
reason = ', '.join(answers)
details.append("diagnosis_mail_blacklist_reason")
details.append("diagnosis_mail_blacklist_website")
yield dict(meta={"test": "mail_blacklist", "item": item,
"blacklist": blacklist["dns_server"]},
data={'blacklist_name': blacklist['name'],
'blacklist_website': blacklist['website'],
'reason': reason},
status="ERROR",
summary='diagnosis_mail_blacklist_listed_by',
details=details)
def check_queue(self):
"""
Check mail queue is not filled with hundreds of email pending
"""
command = 'postqueue -p | grep -v "Mail queue is empty" | grep -c "^[A-Z0-9]" || true'
try:
output = check_output(command).strip()
pending_emails = int(output)
except (ValueError, CalledProcessError) as e:
yield dict(meta={"test": "mail_queue"},
data={"error": str(e)},
status="ERROR",
summary="diagnosis_mail_queue_unavailable",
details="diagnosis_mail_queue_unavailable_details")
else:
if pending_emails > 100:
yield dict(meta={"test": "mail_queue"},
data={'nb_pending': pending_emails},
status="WARNING",
summary="diagnosis_mail_queue_too_big")
else:
yield dict(meta={"test": "mail_queue"},
data={'nb_pending': pending_emails},
status="SUCCESS",
summary="diagnosis_mail_queue_ok")
def get_ips_checked(self):
outgoing_ipversions = []
outgoing_ips = []
ipv4 = Diagnoser.get_cached_report("ip", {"test": "ipv4"}) or {}
if ipv4.get("status") == "SUCCESS":
outgoing_ipversions.append(4)
global_ipv4 = ipv4.get("data", {}).get("global", {})
if global_ipv4:
outgoing_ips.append(global_ipv4)
if settings_get("smtp.allow_ipv6"):
ipv6 = Diagnoser.get_cached_report("ip", {"test": "ipv6"}) or {}
if ipv6.get("status") == "SUCCESS":
outgoing_ipversions.append(6)
global_ipv6 = ipv6.get("data", {}).get("global", {})
if global_ipv6:
outgoing_ips.append(global_ipv6)
return (outgoing_ipversions, outgoing_ips)
def main(args, env, loggers):
return MailDiagnoser(args, env, loggers).diagnose()

View file

@ -17,21 +17,22 @@ class ServicesDiagnoser(Diagnoser):
for service, result in sorted(all_result.items()):
item = dict(meta={"service": service})
item = dict(meta={"service": service},
data={"status": result["status"], "configuration": result["configuration"]})
if result["status"] != "running":
item["status"] = "ERROR"
item["summary"] = ("diagnosis_services_bad_status", {"service": service, "status": result["status"]})
item["details"] = [("diagnosis_services_bad_status_tip", (service,))]
item["summary"] = "diagnosis_services_bad_status"
item["details"] = ["diagnosis_services_bad_status_tip"]
elif result["configuration"] == "broken":
item["status"] = "WARNING"
item["summary"] = ("diagnosis_services_conf_broken", {"service": service})
item["details"] = [(d, tuple()) for d in result["configuration-details"]]
item["summary"] = "diagnosis_services_conf_broken"
item["details"] = result["configuration-details"]
else:
item["status"] = "SUCCESS"
item["summary"] = ("diagnosis_services_running", {"service": service, "status": result["status"]})
item["summary"] = "diagnosis_services_running"
yield item

View file

@ -7,30 +7,34 @@ from yunohost.diagnosis import Diagnoser
class SystemResourcesDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 3600 * 24
cache_duration = 300
dependencies = []
def run(self):
MB = 1024**2
GB = MB*1024
#
# RAM
#
ram = psutil.virtual_memory()
ram_total_abs_MB = ram.total / (1024**2)
ram_available_abs_MB = ram.available / (1024**2)
ram_available_percent = round(100 * ram.available / ram.total)
item = dict(meta={"test": "ram"})
infos = {"total_abs_MB": ram_total_abs_MB, "available_abs_MB": ram_available_abs_MB, "available_percent": ram_available_percent}
if ram_available_abs_MB < 100 or ram_available_percent < 5:
ram_available_percent = 100 * ram.available / ram.total
item = dict(meta={"test": "ram"},
data={"total": human_size(ram.total),
"available": human_size(ram.available),
"available_percent": round_(ram_available_percent)})
if ram.available < 100 * MB or ram_available_percent < 5:
item["status"] = "ERROR"
item["summary"] = ("diagnosis_ram_verylow", infos)
elif ram_available_abs_MB < 200 or ram_available_percent < 10:
item["summary"] = "diagnosis_ram_verylow"
elif ram.available < 200 * MB or ram_available_percent < 10:
item["status"] = "WARNING"
item["summary"] = ("diagnosis_ram_low", infos)
item["summary"] = "diagnosis_ram_low"
else:
item["status"] = "SUCCESS"
item["summary"] = ("diagnosis_ram_ok", infos)
item["summary"] = "diagnosis_ram_ok"
yield item
#
@ -38,20 +42,21 @@ class SystemResourcesDiagnoser(Diagnoser):
#
swap = psutil.swap_memory()
swap_total_abs_MB = swap.total / (1024*1024)
item = dict(meta={"test": "swap"})
infos = {"total_MB": swap_total_abs_MB}
if swap_total_abs_MB <= 0:
item = dict(meta={"test": "swap"},
data={"total": human_size(swap.total), "recommended": "512 MiB"})
if swap.total <= 1 * MB:
item["status"] = "ERROR"
item["summary"] = ("diagnosis_swap_none", infos)
elif swap_total_abs_MB <= 256:
item["summary"] = "diagnosis_swap_none"
elif swap.total <= 512 * MB:
item["status"] = "WARNING"
item["summary"] = ("diagnosis_swap_notsomuch", infos)
item["summary"] = "diagnosis_swap_notsomuch"
else:
item["status"] = "SUCCESS"
item["summary"] = ("diagnosis_swap_ok", infos)
item["summary"] = "diagnosis_swap_ok"
yield item
# FIXME : add a check that swapiness is low if swap is on a sdcard...
#
# Disks usage
#
@ -63,23 +68,54 @@ class SystemResourcesDiagnoser(Diagnoser):
mountpoint = disk_partition.mountpoint
usage = psutil.disk_usage(mountpoint)
free_abs_GB = usage.free / (1024 ** 3)
free_percent = 100 - usage.percent
free_percent = round_(100 - usage.percent)
item = dict(meta={"test": "diskusage", "mountpoint": mountpoint})
infos = {"mountpoint": mountpoint, "device": device, "free_abs_GB": free_abs_GB, "free_percent": free_percent}
if free_abs_GB < 1 or free_percent < 5:
item = dict(meta={"test": "diskusage", "mountpoint": mountpoint},
data={"device": device, "total": human_size(usage.total), "free": human_size(usage.free), "free_percent": free_percent})
# Special checks for /boot partition because they sometimes are
# pretty small and that's kind of okay... (for example on RPi)
if mountpoint.startswith("/boot"):
if usage.free < 10 * MB or free_percent < 10:
item["status"] = "ERROR"
item["summary"] = ("diagnosis_diskusage_verylow", infos)
elif free_abs_GB < 2 or free_percent < 10:
item["summary"] = "diagnosis_diskusage_verylow"
elif usage.free < 20 * MB or free_percent < 20:
item["status"] = "WARNING"
item["summary"] = ("diagnosis_diskusage_low", infos)
item["summary"] = "diagnosis_diskusage_low"
else:
item["status"] = "SUCCESS"
item["summary"] = ("diagnosis_diskusage_ok", infos)
item["summary"] = "diagnosis_diskusage_ok"
else:
if usage.free < 1 * GB or free_percent < 5:
item["status"] = "ERROR"
item["summary"] = "diagnosis_diskusage_verylow"
elif usage.free < 2 * GB or free_percent < 10:
item["status"] = "WARNING"
item["summary"] = "diagnosis_diskusage_low"
else:
item["status"] = "SUCCESS"
item["summary"] = "diagnosis_diskusage_ok"
yield item
def human_size(bytes_):
# Adapted from https://stackoverflow.com/a/1094933
for unit in ['','ki','Mi','Gi','Ti','Pi','Ei','Zi']:
if abs(bytes_) < 1024.0:
return "%s %sB" % (round_(bytes_), unit)
bytes_ /= 1024.0
return "%s %sB" % (round_(bytes_), 'Yi')
def round_(n):
# round_(22.124) -> 22
# round_(9.45) -> 9.4
n = round(n, 1)
if n > 10:
n = int(round(n))
return n
def main(args, env, loggers):
return SystemResourcesDiagnoser(args, env, loggers).diagnose()

View file

@ -4,8 +4,7 @@ import os
import subprocess
from yunohost.diagnosis import Diagnoser
from yunohost.regenconf import manually_modified_files
#from yunohost.regenconf import manually_modified_files, manually_modified_files_compared_to_debian_default
from yunohost.regenconf import _get_regenconf_infos, _calculate_hash
class RegenconfDiagnoser(Diagnoser):
@ -16,28 +15,27 @@ class RegenconfDiagnoser(Diagnoser):
def run(self):
regenconf_modified_files = manually_modified_files()
#debian_modified_files = manually_modified_files_compared_to_debian_default(ignore_handled_by_regenconf=True)
regenconf_modified_files = list(self.manually_modified_files())
if regenconf_modified_files == []:
if not regenconf_modified_files:
yield dict(meta={"test": "regenconf"},
status="SUCCESS",
summary=("diagnosis_regenconf_allgood", {})
summary="diagnosis_regenconf_allgood"
)
else:
for f in regenconf_modified_files:
yield dict(meta={"test": "regenconf", "file": f},
yield dict(meta={"test": "regenconf", "category": f['category'], "file": f['path']},
status="WARNING",
summary=("diagnosis_regenconf_manually_modified", {"file": f}),
details=[("diagnosis_regenconf_manually_modified_details", {})]
summary="diagnosis_regenconf_manually_modified",
details=["diagnosis_regenconf_manually_modified_details"]
)
#for f in debian_modified_files:
# yield dict(meta={"test": "debian", "file": f},
# status="WARNING",
# summary=("diagnosis_regenconf_manually_modified_debian", {"file": f}),
# details=[("diagnosis_regenconf_manually_modified_debian_details", {})]
# )
def manually_modified_files(self):
for category, infos in _get_regenconf_infos().items():
for path, hash_ in infos["conffiles"].items():
if hash_ != _calculate_hash(path):
yield {"path": path, "category": category}
def main(args, env, loggers):

View file

@ -21,13 +21,13 @@ class SecurityDiagnoser(Diagnoser):
if self.is_vulnerable_to_meltdown():
yield dict(meta={"test": "meltdown"},
status="ERROR",
summary=("diagnosis_security_vulnerable_to_meltdown", {}),
details=[("diagnosis_security_vulnerable_to_meltdown_details", ())]
summary="diagnosis_security_vulnerable_to_meltdown",
details=["diagnosis_security_vulnerable_to_meltdown_details"]
)
else:
yield dict(meta={},
status="SUCCESS",
summary=("diagnosis_security_all_good", {})
summary="diagnosis_security_all_good"
)

184
data/other/dnsbl_list.yml Normal file
View file

@ -0,0 +1,184 @@
# Used by GAFAM
- name: Spamhaus ZEN
dns_server: zen.spamhaus.org
website: https://www.spamhaus.org/zen/
ipv4: true
ipv6: true
domain: false
- name: Barracuda Reputation Block List
dns_server: b.barracudacentral.org
website: https://barracudacentral.org/rbl/
ipv4: true
ipv6: false
domain: false
- name: Hostkarma
dns_server: hostkarma.junkemailfilter.com
website: https://ipadmin.junkemailfilter.com/remove.php
ipv4: true
ipv6: false
domain: false
- name: ImproWare IP based spamlist
dns_server: spamrbl.imp.ch
website: https://antispam.imp.ch/
ipv4: true
ipv6: false
domain: false
- name: ImproWare IP based wormlist
dns_server: wormrbl.imp.ch
website: https://antispam.imp.ch/
ipv4: true
ipv6: false
domain: false
- name: Backscatterer.org
dns_server: ips.backscatterer.org
website: http://www.backscatterer.org/
ipv4: true
ipv6: false
domain: false
- name: inps.de
dns_server: dnsbl.inps.de
website: http://dnsbl.inps.de/
ipv4: true
ipv6: false
domain: false
- name: LASHBACK
dns_server: ubl.unsubscore.com
website: https://blacklist.lashback.com/
ipv4: true
ipv6: false
domain: false
- name: Mailspike.org
dns_server: bl.mailspike.net
website: http://www.mailspike.net/
ipv4: true
ipv6: false
domain: false
- name: NiX Spam
dns_server: ix.dnsbl.manitu.net
website: http://www.dnsbl.manitu.net/
ipv4: true
ipv6: false
domain: false
- name: REDHAWK
dns_server: access.redhawk.org
website: https://www.redhawk.org/SpamHawk/query.php
ipv4: true
ipv6: false
domain: false
- name: SORBS Open SMTP relays
dns_server: smtp.dnsbl.sorbs.net
website: http://www.sorbs.net/
ipv4: true
ipv6: false
domain: false
- name: SORBS Spamhost (last 28 days)
dns_server: recent.spam.dnsbl.sorbs.net
website: http://www.sorbs.net/
ipv4: true
ipv6: false
domain: false
- name: SORBS Spamhost (last 48 hours)
dns_server: new.spam.dnsbl.sorbs.net
website: http://www.sorbs.net/
ipv4: true
ipv6: false
domain: false
- name: SpamCop Blocking List
dns_server: bl.spamcop.net
website: https://www.spamcop.net/bl.shtml
ipv4: true
ipv6: false
domain: false
- name: Spam Eating Monkey SEM-BACKSCATTER
dns_server: backscatter.spameatingmonkey.net
website: https://spameatingmonkey.com/services
ipv4: true
ipv6: false
domain: false
- name: Spam Eating Monkey SEM-BLACK
dns_server: bl.spameatingmonkey.net
website: https://spameatingmonkey.com/services
ipv4: true
ipv6: false
domain: false
- name: Spam Eating Monkey SEM-IPV6BL
dns_server: bl.ipv6.spameatingmonkey.net
website: https://spameatingmonkey.com/services
ipv4: false
ipv6: true
domain: false
- name: SpamRATS! all
dns_server: all.spamrats.com
website: http://www.spamrats.com/
ipv4: true
ipv6: false
domain: false
- name: PSBL (Passive Spam Block List)
dns_server: psbl.surriel.com
website: http://psbl.surriel.com/
ipv4: true
ipv6: false
domain: false
- name: SWINOG
dns_server: dnsrbl.swinog.ch
website: https://antispam.imp.ch/
ipv4: true
ipv6: false
domain: false
- name: GBUdb Truncate
dns_server: truncate.gbudb.net
website: http://www.gbudb.com/truncate/index.jsp
ipv4: true
ipv6: false
domain: false
- name: Weighted Private Block List
dns_server: db.wpbl.info
website: http://www.wpbl.info/
ipv4: true
ipv6: false
domain: false
# Used by GAFAM
- name: Composite Blocking List
dns_server: cbl.abuseat.org
website: cbl.abuseat.org
ipv4: true
ipv6: false
domain: false
# Used by GAFAM
- name: SenderScore Blacklist
dns_server: bl.score.senderscore.com
website: https://senderscore.com
ipv4: true
ipv6: false
domain: false
- name: Invaluement
dns_server: sip.invaluement.com
website: https://www.invaluement.com/
ipv4: true
ipv6: false
domain: false
# Added cause it supports IPv6
- name: AntiCaptcha.NET IPv6
dns_server: dnsbl6.anticaptcha.net
website: http://anticaptcha.net/
ipv4: false
ipv6: true
domain: false
- name: SPFBL.net RBL
dns_server: dnsbl.spfbl.net
website: https://spfbl.net/en/dnsbl/
ipv4: true
ipv6: true
domain: true
- name: Suomispam Blacklist
dns_server: bl.suomispam.net
website: http://suomispam.net/
ipv4: true
ipv6: true
domain: false
- name: NordSpam
dns_server: bl.nordspam.com
website: https://www.nordspam.com/
ipv4: true
ipv6: true
domain: false

View file

@ -12,10 +12,25 @@ protocols = imap sieve {% if pop3_enabled == "True" %}pop3{% endif %}
mail_plugins = $mail_plugins quota
ssl = yes
###############################################################################
# generated 2020-04-03, Mozilla Guideline v5.4, Dovecot 2.2.27, OpenSSL 1.1.0l, intermediate configuration
# https://ssl-config.mozilla.org/#server=dovecot&version=2.2.27&config=intermediate&openssl=1.1.0l&guideline=5.4
ssl = required
ssl_cert = </etc/yunohost/certs/{{ main_domain }}/crt.pem
ssl_key = </etc/yunohost/certs/{{ main_domain }}/key.pem
ssl_protocols = !SSLv3
ssl_dh_parameters_length = 2048
# intermediate configuration
ssl_protocols = TLSv1.2
ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
ssl_prefer_server_ciphers = no
###############################################################################
passdb {
args = /etc/dovecot/dovecot-ldap.conf

View file

@ -0,0 +1,5 @@
location ^~ '/.well-known/acme-challenge/'
{
default_type "text/plain";
alias /tmp/acme-challenge-public/;
}

View file

@ -4,5 +4,5 @@ sub_filter_once on;
# Apply to other mime types than text/html
sub_filter_types application/xhtml+xml;
# Prevent YunoHost panel files from being blocked by specific app rules
location ~ (ynh_portal.js|ynh_overlay.css|ynh_userinfo.json) {
location ~ (ynh_portal.js|ynh_overlay.css|ynh_userinfo.json|ynhtheme/custom_portal.js|ynhtheme/custom_overlay.css) {
}

View file

@ -1,24 +1,22 @@
{% if compatibility == "modern" %}
# Ciphers with modern compatibility
# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=modern
# The following configuration use modern ciphers, but remove compatibility with some old clients (android < 5.0, Internet Explorer < 10, ...)
ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
{% else %}
# As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519
ssl_ecdh_curve secp521r1:secp384r1:prime256v1;
ssl_prefer_server_ciphers on;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m; # about 200000 sessions
ssl_session_tickets off;
# nginx 1.10 in stretch doesn't support TLS1.3 and Mozilla doesn't have any
# "modern" config recommendation with it.
# So until buster the modern conf is same as intermediate
{% if compatibility == "modern" %} {% else %} {% endif %}
# Ciphers with intermediate compatibility
# https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.6.2&openssl=1.0.1t&hsts=yes&profile=intermediate
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
# generated 2020-04-03, Mozilla Guideline v5.4, nginx 1.10.3, OpenSSL 1.1.0l, intermediate configuration
# https://ssl-config.mozilla.org/#server=nginx&version=1.10.3&config=intermediate&openssl=1.1.0l&guideline=5.4
ssl_protocols TLSv1.2;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Uncomment the following directive after DH generation
# > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048
#ssl_dhparam /etc/ssl/private/dh2048.pem;
{% endif %}
# Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners
# https://wiki.mozilla.org/Security/Guidelines/Web_Security

View file

@ -10,17 +10,19 @@ server {
access_by_lua_file /usr/share/ssowat/access.lua;
include /etc/nginx/conf.d/acme-challenge.conf.inc;
include /etc/nginx/conf.d/{{ domain }}.d/*.conf;
location /yunohost/admin {
return 301 https://$http_host$request_uri;
}
location /.well-known/ynh-diagnosis/ {
location ^~ '/.well-known/ynh-diagnosis/' {
alias /tmp/.well-known/ynh-diagnosis/;
}
location /.well-known/autoconfig/mail/ {
location ^~ '/.well-known/autoconfig/mail/' {
alias /var/www/.well-known/{{ domain }}/autoconfig/mail/;
}
@ -33,12 +35,10 @@ server {
listen [::]:443 ssl http2;
server_name {{ domain }};
include /etc/nginx/conf.d/security.conf.inc;
ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem;
ssl_certificate_key /etc/yunohost/certs/{{ domain }}/key.pem;
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;
include /etc/nginx/conf.d/security.conf.inc;
{% if domain_cert_ca != "Self-signed" %}
more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload";
@ -52,6 +52,10 @@ server {
resolver_timeout 5s;
{% endif %}
location ^~ '/.well-known/autoconfig/mail/' {
alias /var/www/.well-known/{{ domain }}/autoconfig/mail/;
}
access_by_lua_file /usr/share/ssowat/access.lua;
include /etc/nginx/conf.d/{{ domain }}.d/*.conf;
@ -85,12 +89,10 @@ server {
client_max_body_size 105M; # Choose a value a bit higher than the max upload configured in XMPP server
}
include /etc/nginx/conf.d/security.conf.inc;
ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem;
ssl_certificate_key /etc/yunohost/certs/{{ domain }}/key.pem;
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;
include /etc/nginx/conf.d/security.conf.inc;
{% if domain_cert_ca != "Self-signed" %}
more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload";

View file

@ -18,35 +18,45 @@ append_dot_mydomain = no
readme_directory = no
# -- TLS for incoming connections
# By default, TLS is disabled in the Postfix SMTP server, so no difference to
# plain Postfix is visible. Explicitly switch it on with "smtpd_tls_security_level = may".
smtpd_tls_security_level=may
###############################################################################
# generated 2020-04-03, Mozilla Guideline v5.4, Postfix 3.1.14, OpenSSL 1.1.0l, intermediate configuration
# https://ssl-config.mozilla.org/#server=postfix&version=3.1.14&config=intermediate&openssl=1.1.0l&guideline=5.4
# Sending AUTH data over an unencrypted channel poses a security risk.
# When TLS layer encryption is optional ("smtpd_tls_security_level = may"), it
# may however still be useful to only offer AUTH when TLS is active. To maintain
# compatibility with non-TLS clients, the default is to accept AUTH without
# encryption. In order to change this behavior, we set "smtpd_tls_auth_only = yes".
# (No modern conf support until we're on buster...)
# {% if compatibility == "intermediate" %} {% else %} {% endif %}
smtpd_use_tls = yes
smtpd_tls_security_level = may
smtpd_tls_auth_only = yes
smtpd_tls_cert_file = /etc/yunohost/certs/{{ main_domain }}/crt.pem
smtpd_tls_key_file = /etc/yunohost/certs/{{ main_domain }}/key.pem
smtpd_tls_exclude_ciphers = aNULL, MD5, DES, ADH, RC4, 3DES
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtpd_tls_loglevel=1
{% if compatibility == "intermediate" %}
smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
{% else %}
smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
{% endif %}
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
# smtpd_tls_mandatory_ciphers = medium # (c.f. below)
# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam.pem
# not actually 1024 bits, this applies to all DHE >= 1024 bits
# smtpd_tls_dh1024_param_file = /path/to/dhparam.pem
# This custom medium cipherlist recommendation only works if we have a DH ... which we don't, c.f. https://github.com/YunoHost/issues/issues/93
# On the other hand, the postfix doc strongly discourage tweaking this list ... So whatever, let's keep the mandatory_ciphers to high like we did before applying the Mozilla recommendation ...
#tls_medium_cipherlist = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
tls_preempt_cipherlist = no
# Custom Yunohost stuff ... because we can't use the recommendation about medium cipher list ...
smtpd_tls_mandatory_ciphers=high
smtpd_tls_eecdh_grade = ultra
###############################################################################
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtpd_tls_loglevel=1
# -- TLS for outgoing connections
# Use TLS if this is supported by the remote SMTP server, otherwise use plaintext.
smtp_tls_security_level=may
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtp_tls_exclude_ciphers = $smtpd_tls_exclude_ciphers
smtp_tls_mandatory_ciphers= $smtpd_tls_mandatory_ciphers
smtp_tls_exclude_ciphers = aNULL, MD5, DES, ADH, RC4, 3DES
smtp_tls_mandatory_ciphers= high
smtp_tls_loglevel=1
# Configure Root CA certificates

View file

@ -0,0 +1,9 @@
[Service]
# Prevent slapd from getting killed by oom reaper as much as possible
OOMScoreAdjust=-1000
# If slapd exited (for instance if got killed) the service should not be
# considered as active anymore...
RemainAfterExit=no
# Automatically restart the service if the service gets down
Restart=always
RestartSec=3

View file

@ -13,7 +13,7 @@ metronome:
category: xmpp
mysql:
log: [/var/log/mysql.log,/var/log/mysql.err,/var/log/mysql/error.log]
alternates: ['mariadb']
actual_systemd_service: mariadb
category: database
nginx:
log: /var/log/nginx
@ -27,7 +27,7 @@ php7.0-fpm:
category: web
postfix:
log: [/var/log/mail.log,/var/log/mail.err]
test_status: systemctl show postfix@- | grep -q "^SubState=running"
actual_systemd_service: postfix@-
needs_exposed_ports: [25, 587]
category: email
redis-server:

114
debian/changelog vendored
View file

@ -1,9 +1,115 @@
yunohost (3.8.0~alpha) testing; urgency=low
yunohost (3.8.1.1) testing; urgency=low
Placeholder for upcoming 3.8 to avoid funky stuff with version numbers in
builds etc.
- [fix] Stupid issue about path in debian/install ...
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 16 Mar 2020 01:00:00 +0000
-- Alexandre Aubin <alex.aubin@mailoo.org> Sun, 19 Apr 2020 07:04:00 +0000
yunohost (3.8.1) testing; urgency=low
## Helpers (PHP, apt)
- New helpers for extra apt repo, PHP version install, and PHP fpm (#881, #928, #929)
- Pave the way to migration to php7.3 and future ones (#880, #926)
- Option in PHP helper to use a dedicated php service (#915)
## Diagnosis
- Many improvements in diagnosis mechanism (#923, #921, #940)
## Misc fixes, improvements
- custom_portal and custom_overlay redirect (#925)
- Improve systemd settings for slapd (#933)
- Spelling and typo corrections (#931)
- Improve translations for French, German, Catalan
Thanks to all contributors <3 ! (Kay0u, Maniack Crudelis, ljf, E.Gaspar,
xaloc33)
-- Alexandre Aubin <alex.aubin@mailoo.org> Sun, 19 Apr 2020 06:20:00 +0000
yunohost (3.8.0) testing; urgency=low
# Major stuff
- [enh] New diagnosis system (#534, #872, #919, a416044, a354425, 4ab3653, decb372, e686dc6, b5d18d6, 69bc124, 937d339, cc2288c, aaa9805, 526a3a2)
- [enh] App categories (#778, #853)
- [enh] Support XMPP http upload (#831)
- [enh] Many small improvements in the way we manage services (#838, fa5c0e9, dd92a34, c97a839)
- [enh] Add subcategories management in bash completion (#839)
- [mod] Add conflict with apache2 and bind9, other minor changes in Depends (#909, 3bd6a7a, 0a482fd)
- [enh] Setting to enable POP3 in email stack (#791)
- [enh] Better UX for CLI/API to change maindomain (#796)
# Misc technical
- Update ciphers for nginx, postfix and dovecot according to new Mozilla recommendation (#913, #914)
- Get rid of domain-specific acme-challenge snippet, use a single snippet included in every conf (#917)
- [enh] Persist cookies between multiple ynh_local_curl calls for the same app (#884, #903)
- [fix] ynh_find_port didn't detect port already used on UDP (#827, #907)
- [fix] prevent firefox to mix CA and server certificate (#857)
- [enh] add operation logger for config panel (#869)
- [fix] psql helpers: Revoke sessions before dropping tables (#895)
- [fix] moulinette logs were never displayed #lol (#758)
# Tests, cleaning, refactoring
- Add core CI, improve/fix tests (#856, #863, 6eb8efb, c4590ab, 711cc35, 6c24755)
- Refactoring (#805, 101d3be, #784)
- Drop some very-old deprecated app helpers (though still somewhat supporting them through hacky patching) (#780)
- Drop glances and the old monitoring system (#821)
- Drop app_debug (#824)
- Drop app's status.json (#834)
- Drop ynh_add_skipped/(un)protected_uris helpers (#910)
- Use a common security.conf.inc instead of having cipher setting in each nginx's domain file (1285776, 4d99cbe, be8427d, 22b9565)
- Don't add weird tmp redirected_urls after postinstall (#902)
- Don't do weird stuff with yunohost-firewall during debian's postinst (978d9d5)
# i18n, messaging
- Unit tests / lint / cleaning for translation files (#901)
- Improve message wording, spelling (8b0c9e5, 9fe43b1, f69ab4c, 0decb64, 986f38f, 8d40c73, 8fe343a, 1d84f17)
- Improve translations for French, Catalan, Bengali (Bangladesh), Italian, Dutch, Norwegian Bokmål, Chinese, Occitan, Spanish, Esperanto, German, Nepali, Portuguese, Arabic, Russian, Hungarian, Hindi, Polish, Greek
Thanks to all contributors <3 ! (Aeris One, Aleks, Allan N., Alvaro, Armando F., Arthur L., Augustin T., Bram, ButterflyOfFire, Damien P., Gustavo M., Jeroen F., Jimmy M., Josué, Kay0u, Maniack Crudelis, Mario, Matthew D., Mélanie C., Patrick B., Quentí, Yasss Gurl, amirale qt, Elie G., ljf, pitchum, Romain R., tituspijean, xaloc33, yalh76)
-- Kay0u <pierre@kayou.io> Thu, 09 Apr 2020 19:59:18 +0000
yunohost (3.7.1.1) stable; urgency=low
- [fix] lxc uid number is limited to 65536 by default (0c9a4509)
- [fix] also invalidate group cache when creating users (aaabf8c7)
- [fix] Make sure to have a path that include sbin for stupid cron jobs (f03bb82a)
-- Alexandre Aubin <alex.aubin@mailoo.org> Sun, 12 Apr 2020 23:15:00 +0000
yunohost (3.7.1) stable; urgency=low
- [enh] Add ynh_permission_has_user helper (#905)
- [mod] Change behavior of ynh_setting_delete to try to make migrating away from legacy permissions easier (#906)
- [fix] app_config_apply should also return 'app' info (#918)
- [fix] uid/gid conflicts in user_create because of inconsistent comparison (#924)
- [fix] Ensure metronome owns its directories (1f623830, 031f8a6e)
- [mod] Remove useless sudos in helpers (be88a283)
- [enh] Improve message wording for services (3c844292)
- [enh] Attempt to anonymize data pasted to paste.yunohost.org (f56f4724)
- [enh] Lazy load yunohost.certificate to possibly improve perfs (af8981e4)
- [fix] Improve logging / debugging (1eef9b67, 7d323814, d17fcaf9, 210d5f3f)
Thanks to all contributors <3 ! (Bram, Kay0u, Maniack, Matthew D.)
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 9 Apr 2020 14:52:00 +0000
yunohost (3.7.0.12) stable; urgency=low
- Fix previous buggy hotfix about deleting existing primary groups ...
-- Alexandre Aubin <alex.aubin@mailoo.org> Sat, 28 Mar 2020 14:52:00 +0000
yunohost (3.7.0.11) stable; urgency=low
- [fix] Mess due to automatic translation tools ~_~
-- Kay0u <pierre@kayou.io> Fri, 27 Mar 2020 23:49:45 +0000
yunohost (3.7.0.10) stable; urgency=low

25
debian/control vendored
View file

@ -15,22 +15,23 @@ Depends: ${python:Depends}, ${misc:Depends}
, python-psutil, python-requests, python-dnspython, python-openssl
, python-apt, python-miniupnpc, python-dbus, python-jinja2
, python-toml
, apt, apt-transport-https, lsb-release
, dnsutils, bind9utils, unzip, git, curl, cron, wget, jq
, ca-certificates, netcat-openbsd, iproute2
, apt, apt-transport-https
, nginx, nginx-extras (>=1.6.2)
, php-fpm, php-ldap, php-intl
, mariadb-server, php-mysql | php-mysqlnd
, openssh-server, iptables, fail2ban, dnsutils, bind9utils
, openssl, ca-certificates, netcat-openbsd, iproute2
, slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd
, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail, mailutils, postsrsd
, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved
, dovecot-antispam, fail2ban, iptables
, nginx-extras (>=1.6.2), php-fpm, php-ldap, php-intl
, dnsmasq, openssl, avahi-daemon, libnss-mdns, resolvconf, libnss-myhostname
, dnsmasq, avahi-daemon, libnss-mdns, resolvconf, libnss-myhostname
, postfix, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre
, dovecot-core, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved, dovecot-antispam
, rspamd (>= 1.6.0), opendkim-tools, postsrsd, procmail, mailutils
, redis-server
, metronome
, rspamd (>= 1.6.0), redis-server, opendkim-tools
, haveged, fake-hwclock
, equivs, lsof
, git, curl, wget, cron, unzip, jq
, lsb-release, haveged, fake-hwclock, equivs, lsof
Recommends: yunohost-admin
, openssh-server, ntp, inetutils-ping | iputils-ping
, ntp, inetutils-ping | iputils-ping
, bash-completion, rsyslog
, php-gd, php-curl, php-gettext, php-mcrypt
, python-pip

1
debian/install vendored
View file

@ -7,6 +7,7 @@ data/hooks/* /usr/share/yunohost/hooks/
data/other/yunoprompt.service /etc/systemd/system/
data/other/password/* /usr/share/yunohost/other/password/
data/other/dpkg-origins/yunohost /etc/dpkg/origins
data/other/dnsbl_list.yml /usr/share/yunohost/other/
data/other/* /usr/share/yunohost/yunohost-config/moulinette/
data/templates/* /usr/share/yunohost/templates/
data/helpers /usr/share/yunohost/

View file

@ -162,7 +162,7 @@
"app_action_broke_system": "يبدو أنّ هذا الإجراء أدّى إلى تحطيم هذه الخدمات المهمة: {services}",
"diagnosis_basesystem_host": "هذا الخادم يُشغّل ديبيان {debian_version}",
"diagnosis_basesystem_kernel": "هذا الخادم يُشغّل نواة لينكس {kernel_version}",
"diagnosis_basesystem_ynh_single_version": "{0} الإصدار: {1} ({2})",
"diagnosis_basesystem_ynh_single_version": "{package} الإصدار: {version} ({repo})",
"diagnosis_basesystem_ynh_main_version": "هذا الخادم يُشغّل YunoHost {main_version} ({repo})",
"diagnosis_everything_ok": "كل شيء على ما يرام في {category}!",
"diagnosis_ip_connected_ipv4": "الخادم مُتّصل بالإنترنت عبر IPv4!",

View file

@ -502,15 +502,16 @@
"permission_require_account": "El permís {permission} només té sentit per als usuaris que tenen un compte, i per tant no es pot activar per als visitants.",
"app_remove_after_failed_install": "Eliminant l'aplicació després que hagi fallat la instal·lació…",
"diagnosis_basesystem_ynh_main_version": "El servidor funciona amb YunoHost {main_version} ({repo})",
"diagnosis_ram_low": "El sistema només té {available_abs_MB} MB ({available_percent}%) de memòria RAM disponibles d'un total de {total_abs_MB} MB. Aneu amb compte.",
"diagnosis_swap_none": "El sistema no té swap. Hauríeu de considerar afegir un mínim de 256 MB de swap per evitar situacions en les que el sistema es queda sense memòria.",
"diagnosis_ram_low": "El sistema només té {available} ({available_percent}%) de memòria RAM disponibles d'un total de {total}. Aneu amb compte.",
"diagnosis_swap_none": "El sistema no té swap. Hauríeu de considerar afegir un mínim de {recommended} de swap per evitar situacions en les que el sistema es queda sense memòria.",
"diagnosis_regenconf_manually_modified": "El fitxer de configuració {file} ha estat modificat manualment.",
"diagnosis_security_vulnerable_to_meltdown_details": "Per arreglar-ho, hauríeu d'actualitzar i reiniciar el sistema per tal de carregar el nou nucli de linux (o contactar amb el proveïdor del servidor si no funciona). Vegeu https://meltdownattack.com/ per a més informació.",
"diagnosis_http_could_not_diagnose": "No s'ha pogut diagnosticar si el domini és accessible des de l'exterior. Error: {error}",
"diagnosis_http_could_not_diagnose": "No s'ha pogut diagnosticar si el domini és accessible des de l'exterior.",
"diagnosis_http_could_not_diagnose_details": "Error: {error}",
"domain_cannot_remove_main_add_new_one": "No es pot eliminar «{domain:s}» ja que és el domini principal i únic domini, primer s'ha d'afegir un altre domini utilitzant «yunohost domain add <un-altre-domini.com>», i després fer-lo el domini principal amb «yunohost domain main-domain -n <un-altre-domini.com>» i després es pot eliminar el domini «{domain:s}» utilitzant «yunohost domain remove {domain:s}».",
"diagnosis_basesystem_host": "El servidor funciona amb Debian {debian_version}",
"diagnosis_basesystem_kernel": "El servidor funciona amb el nucli de Linux {kernel_version}",
"diagnosis_basesystem_ynh_single_version": "{0} versió: {1}({2})",
"diagnosis_basesystem_ynh_single_version": "{package} versió: {version}({repo})",
"diagnosis_basesystem_ynh_inconsistent_versions": "Esteu utilitzant versions inconsistents dels paquets de YunoHost… probablement a causa d'una actualització fallida o parcial.",
"diagnosis_display_tip_web": "Podeu anar a la secció de Diagnòstics (en la pantalla principal) per veure els errors que s'han trobat.",
"diagnosis_failed_for_category": "Ha fallat el diagnòstic per la categoria «{category}»: {error}",
@ -535,16 +536,16 @@
"diagnosis_ip_weird_resolvconf_details": "En canvi, aquest fitxer hauria de ser un enllaç simbòlic cap a /etc/resolvconf/run/resolv.conf i que aquest apunti cap a 127.0.0.1 (dnsmasq). La configuració del «resolver» real s'hauria de fer a /etc/resolv.dnsmaq.conf.",
"diagnosis_dns_good_conf": "Bona configuració DNS pel domini {domain} (categoria {category})",
"diagnosis_dns_bad_conf": "Configuració DNS incorrecta o inexistent pel domini {domain} (categoria {category})",
"diagnosis_dns_missing_record": "Segons la configuració DNS recomanada, hauríeu d'afegir un registre DNS de tipus {0}, nom {1} i valor {2}. Hi ha més informació a https://yunohost.org/dns_config.",
"diagnosis_dns_discrepancy": "El registre DNS de tipus {0} i nom {1} no concorda amb la configuració recomanada. Valor actual: {2}. Valor esperat: {3}. Més informació a https://yunohost.org/dns_config.",
"diagnosis_dns_missing_record": "Segons la configuració DNS recomanada, hauríeu d'afegir un registre DNS\ntipus: {type}\nnom: {name}\nvalor: {value}.",
"diagnosis_dns_discrepancy": "El registre DNS de tipus {type} i nom {name} no concorda amb la configuració recomanada.\nValor actual: {current}\nValor esperat: {value}",
"diagnosis_services_bad_status": "El servei {service} està {status} :(",
"diagnosis_diskusage_verylow": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) només té disponibles {free_abs_GB} GB ({free_percent}%). Hauríeu de considerar alliberar una mica d'espai.",
"diagnosis_diskusage_low": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) només té disponibles {free_abs_GB} GB ({free_percent}%). Aneu amb compte.",
"diagnosis_diskusage_ok": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) encara té {free_abs_GB} GB ({free_percent}%) lliures!",
"diagnosis_ram_verylow": "El sistema només té {available_abs_MB} MB ({available_percent}%) de memòria RAM disponibles! (d'un total de {total_abs_MB} MB)",
"diagnosis_ram_ok": "El sistema encara té {available_abs_MB} MB ({available_percent}%) de memòria RAM disponibles d'un total de {total_abs_MB} MB.",
"diagnosis_swap_notsomuch": "El sistema només té {total_MB} MB de swap. Hauríeu de considerar tenir un mínim de 256 MB per evitar situacions en les que el sistema es queda sense memòria.",
"diagnosis_swap_ok": "El sistema té {total_MB} MB de swap!",
"diagnosis_diskusage_verylow": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) només té disponibles {free} ({free_percent}%). Hauríeu de considerar alliberar una mica d'espai.",
"diagnosis_diskusage_low": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) només té disponibles {free} ({free_percent}%). Aneu amb compte.",
"diagnosis_diskusage_ok": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) encara té {free} ({free_percent}%) lliures!",
"diagnosis_ram_verylow": "El sistema només té {available} ({available_percent}%) de memòria RAM disponibles! (d'un total de {total})",
"diagnosis_ram_ok": "El sistema encara té {available} ({available_percent}%) de memòria RAM disponibles d'un total de {total}.",
"diagnosis_swap_notsomuch": "El sistema només té {total} de swap. Hauríeu de considerar tenir un mínim de {recommended} per evitar situacions en les que el sistema es queda sense memòria.",
"diagnosis_swap_ok": "El sistema té {total} de swap!",
"diagnosis_regenconf_allgood": "Tots els fitxers de configuració estan en acord amb la configuració recomanada!",
"diagnosis_regenconf_manually_modified_details": "No hauria de ser cap problema sempre i quan sapigueu el que esteu fent ;) !",
"diagnosis_regenconf_manually_modified_debian": "El fitxer de configuració {file} ha estat modificat manualment respecte al fitxer per defecte de Debian.",
@ -559,7 +560,8 @@
"diagnosis_description_ports": "Exposició dels ports",
"diagnosis_description_regenconf": "Configuració del sistema",
"diagnosis_description_security": "Verificacions de seguretat",
"diagnosis_ports_could_not_diagnose": "No s'ha pogut diagnosticar si els ports són accessibles des de l'exterior. Error: {error}",
"diagnosis_ports_could_not_diagnose": "No s'ha pogut diagnosticar si els ports són accessibles des de l'exterior.",
"diagnosis_ports_could_not_diagnose_details": "Error: {error}",
"diagnosis_ports_unreachable": "El port {port} no és accessible des de l'exterior.",
"diagnosis_ports_ok": "El port {port} és accessible des de l'exterior.",
"diagnosis_http_ok": "El domini {domain} és accessible per mitjà de HTTP des de fora de la xarxa local.",
@ -571,22 +573,21 @@
"apps_catalog_obsolete_cache": "La memòria cau del catàleg d'aplicacions és buida o obsoleta.",
"apps_catalog_update_success": "S'ha actualitzat el catàleg d'aplicacions!",
"diagnosis_mail_ougoing_port_25_ok": "El port de sortida 25 no està bloquejat i els correus es poden enviar a altres servidors.",
"diagnosis_mail_ougoing_port_25_blocked": "Sembla que el port de sortida 25 està bloquejat. Hauríeu d'intentar desbloquejar-lo al panell de configuració del proveïdor d'accés a internet (o allotjador). Mentrestant, el servidor no podrà enviar correus a altres servidors.",
"diagnosis_mail_outgoing_port_25_blocked": "Sembla que el port de sortida 25 està bloquejat. Hauríeu d'intentar desbloquejar-lo al panell de configuració del proveïdor d'accés a internet (o allotjador). Mentrestant, el servidor no podrà enviar correus a altres servidors.",
"diagnosis_description_mail": "Correu electrònic",
"migration_description_0013_futureproof_apps_catalog_system": "Migrar al nou sistema de catàleg d'aplicacions resistent al pas del temps",
"app_upgrade_script_failed": "Hi ha hagut un error en el script d'actualització de l'aplicació",
"diagnosis_services_bad_status_tip": "Podeu intentar reiniciar el servei, i si no funciona, podeu mirar els registres del servei utilitzant «yunohost service log {0}» o a través de «Serveis» a la secció de la pàgina web d'administració.",
"diagnosis_services_bad_status_tip": "Podeu intentar reiniciar el servei, i si no funciona, podeu mirar els registres del servei utilitzant «yunohost service log {service}» o a través de «Serveis» a la secció de la pàgina web d'administració.",
"diagnosis_ports_forwarding_tip": "Per arreglar aquest problema, segurament s'ha de configurar el reenviament de ports en el router tal i com s'explica a https://yunohost.org/isp_box_config",
"diagnosis_http_bad_status_code": "El sistema de diagnòstic no ha pogut connectar amb el servidor. Podria ser que una altra màquina hagi contestat en lloc del servidor. S'hauria de comprovar que el reenviament del port 80 sigui correcte, que la configuració NGINX està actualitzada i que el reverse-proxy no està interferint.",
"diagnosis_no_cache": "Encara no hi ha memòria cau pel diagnòstic de la categoria «{category}»",
"diagnosis_http_timeout": "S'ha exhaurit el temps d'esperar intentant connectar amb el servidor des de l'exterior. Sembla que no s'hi pot accedir. S'hauria de comprovar que el reenviament del port 80 és correcte, que NGINX funciona, i que el tallafocs no està interferint.",
"diagnosis_http_connection_error": "Error de connexió: no s'ha pogut connectar amb el domini demanat, segurament és inaccessible.",
"diagnosis_http_unknown_error": "Hi ha hagut un error intentant accedir al domini, segurament és inaccessible.",
"yunohost_postinstall_end_tip": "S'ha completat la post-instal·lació. Per acabar la configuració, considereu:\n - afegir un primer usuari a través de la secció «Usuaris» a la pàgina web d'administració (o emprant «yunohost user create <username>» a la línia d'ordres);\n - diagnosticar possibles problemes a través de la secció «Diagnòstics» a la pàgina web d'administració (o emprant «yunohost diagnosis run» a la línia d'ordres);\n - llegir les seccions «Finalizing your setup» i «Getting to know Yunohost» a la documentació per administradors: https://yunohost.org/admindoc.",
"migration_description_0014_remove_app_status_json": "Eliminar els fitxers d'aplicació status.json heretats",
"diagnosis_services_running": "El servei {service} s'està executant!",
"diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!",
"diagnosis_ports_needed_by": "És necessari exposar aquest port per a les funcions {1} (servei {0})",
"diagnosis_ports_needed_by": "És necessari exposar aquest port per a les funcions {category} (servei {service})",
"global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu",
"log_app_action_run": "Executa l'acció de l'aplicació «{}»",
"log_app_config_show_panel": "Mostra el taulell de configuració de l'aplicació «{}»",

View file

@ -304,7 +304,7 @@
"app_upgrade_script_failed": "Es ist ein Fehler im App-Upgrade-Skript aufgetreten",
"diagnosis_basesystem_host": "Server läuft unter Debian {debian_version}.",
"diagnosis_basesystem_kernel": "Server läuft unter Linux-Kernel {kernel_version}",
"diagnosis_basesystem_ynh_single_version": "{0} Version: {1} ({2})",
"diagnosis_basesystem_ynh_single_version": "{package} Version: {version} ({repo})",
"diagnosis_basesystem_ynh_main_version": "Server läuft YunoHost {main_version} ({repo})",
"diagnosis_basesystem_ynh_inconsistent_versions": "Sie verwenden inkonsistente Versionen der YunoHost-Pakete... wahrscheinlich wegen eines fehlgeschlagenen oder teilweisen Upgrades.",
"diagnosis_display_tip_web": "Sie können den Abschnitt Diagnose (im Startbildschirm) aufrufen, um die gefundenen Probleme anzuzeigen.",

View file

@ -110,7 +110,7 @@
"backup_unable_to_organize_files": "Could not use the quick method to organize files in the archive",
"backup_with_no_backup_script_for_app": "The app '{app:s}' has no backup script. Ignoring.",
"backup_with_no_restore_script_for_app": "The '{app:s}' has no restoration script, you will not be able to automatically restore the backup of this app.",
"certmanager_acme_not_configured_for_domain": "Certificate for the domain '{domain:s}' does not appear to be correctly installed. Please run 'cert-install' for this domain first.",
"certmanager_acme_not_configured_for_domain": "The ACME challenge cannot be ran for {domain} right now because its nginx conf lacks the corresponding code snippet... Please make sure that your nginx configuration is up to date using `yunohost tools regen-conf nginx --dry-run --with-diff`.",
"certmanager_attempt_to_renew_nonLE_cert": "The certificate for the domain '{domain:s}' is not issued by Let's Encrypt. Cannot renew it automatically!",
"certmanager_attempt_to_renew_valid_cert": "The certificate for the domain '{domain:s}' is not about to expire! (You may use --force if you know what you're doing)",
"certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)",
@ -120,7 +120,6 @@
"certmanager_cert_renew_success": "Let's Encrypt certificate renewed for the domain '{domain:s}'",
"certmanager_cert_signing_failed": "Could not sign the new certificate",
"certmanager_certificate_fetching_or_enabling_failed": "Trying to use the new certificate for {domain:s} did not work…",
"certmanager_conflicting_nginx_file": "Could not prepare domain for ACME challenge: the NGINX configuration file {filepath:s} is conflicting and should be removed first",
"certmanager_couldnt_fetch_intermediate_cert": "Timed out when trying to fetch intermediate certificate from Let's Encrypt. Certificate installation/renewal aborted—please try again later.",
"certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain:s} is not self-signed. Are you sure you want to replace it? (Use '--force' to do so.)",
"certmanager_domain_dns_ip_differs_from_public_ip": "The DNS 'A' record for the domain '{domain:s}' is different from this server's IP. If you recently modified your A record, please wait for it to propagate (some DNS propagation checkers are available online). (If you know what you are doing, use '--no-checks' to turn off those checks.)",
@ -141,13 +140,12 @@
"diagnosis_basesystem_hardware_board": "Server board model is {model}",
"diagnosis_basesystem_host": "Server is running Debian {debian_version}",
"diagnosis_basesystem_kernel": "Server is running Linux kernel {kernel_version}",
"diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})",
"diagnosis_basesystem_ynh_single_version": "{package} version: {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_display_tip_web": "You can go to the Diagnosis section (in the home screen) to see the issues found.",
"diagnosis_display_tip_cli": "You can run 'yunohost diagnosis show --issues' to display the issues found.",
"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_cache_still_valid": "(Cache still valid for {category} diagnosis. Not re-diagnosing yet!)",
"diagnosis_cache_still_valid": "(Cache still valid for {category} diagnosis. Won't re-diagnose it yet!)",
"diagnosis_cant_run_because_of_dep": "Can't run diagnosis for {category} while there are important issues related to {dep}.",
"diagnosis_ignored_issues": "(+ {nb_ignored} ignored issue(s))",
"diagnosis_found_errors": "Found {errors} significant issue(s) related to {category}!",
@ -160,36 +158,63 @@
"diagnosis_ip_no_ipv4": "The server does not have working IPv4.",
"diagnosis_ip_connected_ipv6": "The server is connected to the Internet through IPv6 !",
"diagnosis_ip_no_ipv6": "The server does not have working IPv6.",
"diagnosis_ip_global": "Global IP: <code>{global}</code>",
"diagnosis_ip_local": "Local IP: <code>{local}</code>",
"diagnosis_ip_not_connected_at_all": "The server does not seem to be connected to the Internet at all!?",
"diagnosis_ip_dnsresolution_working": "Domain name resolution is working!",
"diagnosis_ip_broken_dnsresolution": "Domain name resolution seems to be broken for some reason... Is a firewall blocking DNS requests ?",
"diagnosis_ip_broken_resolvconf": "Domain name resolution seems to be broken on your server, which seems related to /etc/resolv.conf not pointing to 127.0.0.1.",
"diagnosis_ip_weird_resolvconf": "DNS resolution seems to be working, but be careful that you seem to be using a custom /etc/resolv.conf.",
"diagnosis_ip_weird_resolvconf_details": "Instead, this file should be a symlink to /etc/resolvconf/run/resolv.conf itself pointing to 127.0.0.1 (dnsmasq). The actual resolvers should be configured in /etc/resolv.dnsmasq.conf.",
"diagnosis_dns_good_conf": "Good DNS configuration for domain {domain} (category {category})",
"diagnosis_dns_bad_conf": "Bad or missing DNS configuration for domain {domain} (category {category})",
"diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with type {0}, name {1} and value {2}. You can check https://yunohost.org/dns_config for more info.",
"diagnosis_dns_discrepancy": "The DNS record with type {0} and name {1} does not match the recommended configuration. Current value: {2}. Excepted value: {3}. You can check https://yunohost.org/dns_config for more info.",
"diagnosis_ip_broken_resolvconf": "Domain name resolution seems to be broken on your server, which seems related to <code>/etc/resolv.conf</code> not pointing to <code>127.0.0.1</code>.",
"diagnosis_ip_weird_resolvconf": "DNS resolution seems to be working, but it looks like you're using a custom <code>/etc/resolv.conf</code>.",
"diagnosis_ip_weird_resolvconf_details": "The file <code>/etc/resolv.conf</code> should be a symlink to <code>/etc/resolvconf/run/resolv.conf</code> itself pointing to <code>127.0.0.1</code> (dnsmasq). If you want to manually configure DNS resolvers, please edit <code>/etc/resolv.dnsmasq.conf</code>.",
"diagnosis_dns_good_conf": "DNS records are correctly configured for domain {domain} (category {category})",
"diagnosis_dns_bad_conf": "Some DNS records are missing or incorrect for domain {domain} (category {category})",
"diagnosis_dns_missing_record": "According to the recommended DNS configuration, you should add a DNS record with the following info.<br>Type: <code>{type}</code><br>Name: <code>{name}</code><br>Value: <code>{value}</code>",
"diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:<br>Type: <code>{type}</code><br>Name: <code>{name}</code><br>Current value: <code>{current}</code><br>Excepted value: <code>{value}</code>",
"diagnosis_dns_point_to_doc": "Please check the documentation at <a href='https://yunohost.org/dns_config'>https://yunohost.org/dns_config</a> if you need help about configuring DNS records.",
"diagnosis_services_running": "Service {service} is running!",
"diagnosis_services_conf_broken": "Configuration is broken for service {service}!",
"diagnosis_services_bad_status": "Service {service} is {status} :(",
"diagnosis_services_bad_status_tip": "You can try to restart the service, and if it doesn't work, have a look at the service logs using 'yunohost service log {0}' or through the 'Services' section of the webadmin.",
"diagnosis_diskusage_verylow": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. You should really consider cleaning up some space.",
"diagnosis_diskusage_low": "Storage {mountpoint} (on device {device}) has only {free_abs_GB} GB ({free_percent}%) space remaining. Be careful.",
"diagnosis_diskusage_ok": "Storage {mountpoint} (on device {device}) still has {free_abs_GB} GB ({free_percent}%) space left!",
"diagnosis_ram_verylow": "The system has only {available_abs_MB} MB ({available_percent}%) RAM left! (out of {total_abs_MB} MB)",
"diagnosis_ram_low": "The system has {available_abs_MB} MB ({available_percent}%) RAM left out of {total_abs_MB} MB. Be careful.",
"diagnosis_ram_ok": "The system still has {available_abs_MB} MB ({available_percent}%) RAM left out of {total_abs_MB} MB.",
"diagnosis_swap_none": "The system has no swap at all. You should consider adding at least 256 MB of swap to avoid situations where the system runs out of memory.",
"diagnosis_swap_notsomuch": "The system has only {total_MB} MB swap. You should consider having at least 256 MB to avoid situations where the system runs out of memory.",
"diagnosis_swap_ok": "The system has {total_MB} MB of swap!",
"diagnosis_mail_ougoing_port_25_ok": "Outgoing port 25 is not blocked and email can be sent to other servers.",
"diagnosis_mail_ougoing_port_25_blocked": "Outgoing port 25 appears to be blocked. You should try to unblock it in your internet service provider (or hosting provider) configuration panel. Meanwhile, the server won't be able to send emails to other servers.",
"diagnosis_services_bad_status_tip": "You can try to <a href='#/services/{service}'>restart the service</a>, and if it doesn't work, have a look at <a href='#/services/{service}'>the service logs in the webadmin</a> (from the command line, you can do this with <cmd>yunohost service restart {service}</cmd> and <cmd>yunohost service log {service}</cmd>).",
"diagnosis_diskusage_verylow": "Storage <code>{mountpoint}</code> (on device <code>{device}</code>) has only {free} ({free_percent}%) space remaining (out of {total}). You should really consider cleaning up some space!",
"diagnosis_diskusage_low": "Storage <code>{mountpoint}</code> (on device <code>{device}</code>) has only {free} ({free_percent}%) space remaining (out of {total}). Be careful.",
"diagnosis_diskusage_ok": "Storage <code>{mountpoint}</code> (on device <code>{device}</code>) still has {free} ({free_percent}%) space left (out of {total})!",
"diagnosis_ram_verylow": "The system has only {available} ({available_percent}%) RAM available! (out of {total})",
"diagnosis_ram_low": "The system has {available} ({available_percent}%) RAM available (out of {total}). Be careful.",
"diagnosis_ram_ok": "The system still has {available} ({available_percent}%) RAM available out of {total}.",
"diagnosis_swap_none": "The system has no swap at all. You should consider adding at least {recommended} of swap to avoid situations where the system runs out of memory.",
"diagnosis_swap_notsomuch": "The system has only {total} swap. You should consider having at least {recommended} to avoid situations where the system runs out of memory.",
"diagnosis_swap_ok": "The system has {total} of swap!",
"diagnosis_mail_outgoing_port_25_ok": "The SMTP mail server is able to send emails (outgoing port 25 is not blocked).",
"diagnosis_mail_outgoing_port_25_blocked": "The SMTP mail server cannot send emails to other servers because outgoing port 25 is blocked in IPv{ipversion}.",
"diagnosis_mail_outgoing_port_25_blocked_details": "You should first try to unblock outgoing port 25 in your internet router interface or your hosting provider interface. (Some hosting provider may require you to send them a support ticket for this).",
"diagnosis_mail_outgoing_port_25_blocked_relay_vpn": "Some providers won't let you unblock outgoing port 25 because they don't care about Net Neutrality.<br> - Some of them provide the alternative of <a href='https://yunohost.org/#/smtp_relay'>using a mail server relay</a> though it implies that the relay will be able to spy on your email traffic.<br>- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See <a href='https://yunohost.org/#/vpn_advantage'>https://yunohost.org/#/vpn_advantage</a><br>- You can also consider switching to <a href='https://yunohost.org/#/isp'>a more net neutrality-friendly provider</a>",
"diagnosis_mail_ehlo_ok": "The SMTP mail server is reachable from the outside and therefore is able to receive emails!",
"diagnosis_mail_ehlo_unreachable": "The SMTP mail server is unreachable from the outside on IPv{ipversion}. It won't be able to receive emails.",
"diagnosis_mail_ehlo_unreachable_details": "Could not open a connection on port 25 to your server in IPv{ipversion}. It appears to be unreachable.<br>1. The most common cause for this issue is that port 25 <a href='https://yunohost.org/isp_box_config'>is not correctly forwarded to your server</a>.<br>2. You should also make sure that service postfix is running.<br>3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.",
"diagnosis_mail_ehlo_bad_answer": "A non-SMTP service answered on port 25 on IPv{ipversion}",
"diagnosis_mail_ehlo_bad_answer_details": "It could be due to an other machine answering instead of your server.",
"diagnosis_mail_ehlo_wrong": "A different SMTP mail server answers on IPv{ipversion}. It will probably not be able to receive emails.",
"diagnosis_mail_ehlo_wrong_details": "The EHLO received by the remote diagnoser in IPv{ipversion} is different from your server's domain.<br>Received EHLO: <code>{wrong_ehlo}</code><br>Expected: {right_ehlo}<br>The most common cause for this issue is that port 25 <a href='https://yunohost.org/isp_box_config'>is not correctly forwarded to your server</a>. Alternatively, make sure that no firewall or reverse-proxy is interfering.",
"diagnosis_mail_ehlo_could_not_diagnose": "Could not diagnose if postfix mail server is reachable from outside in IPv{ipversion}.",
"diagnosis_mail_ehlo_could_not_diagnose_details": "Error: {error}",
"diagnosis_mail_fcrdns_ok": "Your reverse DNS is correctly configured!",
"diagnosis_mail_fcrdns_dns_missing": "No reverse DNS is defined in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.",
"diagnosis_mail_fcrdns_nok_details": "You should first try to configure the reverse DNS with <code>{ehlo_domain}</code> in your internet router interface or your hosting provider interface. (Some hosting provider may require you to send them a support ticket for this).",
"diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If you are experiencing issues because of this, consider the following solutions:<br> - Some ISP provide the alternative of <a href='https://yunohost.org/#/smtp_relay'>using a mail server relay</a> though it implies that the relay will be able to spy on your email traffic.<br>- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See <a href='https://yunohost.org/#/vpn_advantage'>https://yunohost.org/#/vpn_advantage</a><br>- Finally, it's also possible to <a href='https://yunohost.org/#/isp'>change of provider</a>",
"diagnosis_mail_fcrdns_nok_alternatives_6": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If your reverse DNS is correctly configured for IPv4, you can try disabling the use of IPv6 when sending emails by running <cmd>yunohost settings set smtp.allow_ipv6 -v off</cmd>. Note: this last solution means that you won't be able to send or receive emails from the few IPv6-only servers out there.",
"diagnosis_mail_fcrdns_different_from_ehlo_domain": "The reverse DNS is not correctly configured in IPv{ipversion}. Some emails may fail to get delivered or may get flagged as spam.",
"diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Current reverse DNS: <code>{rdns_domain}</code><br>Expected value: <code>{ehlo_domain}</code>",
"diagnosis_mail_blacklist_ok": "The IPs and domains used by this server do not appear to be blacklisted",
"diagnosis_mail_blacklist_listed_by": "Your IP or domain <code>{item}</code> is blacklisted on {blacklist_name}",
"diagnosis_mail_blacklist_reason": "The blacklist reason is: {reason}",
"diagnosis_mail_blacklist_website": "After identifying why you are listed and fixed it, feel free to ask for delisting on {blacklist_website}",
"diagnosis_mail_queue_ok": "{nb_pending} pending emails in the mail queues",
"diagnosis_mail_queue_unavailable": "Can not consult number of pending emails in queue",
"diagnosis_mail_queue_unavailable_details": "Error: {error}",
"diagnosis_mail_queue_too_big": "Too many pending emails in mail queue ({nb_pending} emails)",
"diagnosis_regenconf_allgood": "All configurations files are in line with the recommended configuration!",
"diagnosis_regenconf_manually_modified": "Configuration file {file} was manually modified.",
"diagnosis_regenconf_manually_modified_details": "This is probably OK as long as you know what you're doing ;) !",
"diagnosis_regenconf_manually_modified_debian": "Configuration file {file} was manually modified compared to Debian's default.",
"diagnosis_regenconf_manually_modified_debian_details": "This may probably be OK, but gotta keep an eye on it...",
"diagnosis_regenconf_manually_modified": "Configuration file <code>{file}</code> appears to have been manually modified.",
"diagnosis_regenconf_manually_modified_details": "This is probably OK if you know what you're doing! YunoHost will stop updating this file automatically... But beware that YunoHost upgrades could contain important recommended changes. If you want to, you can inspect the differences with <cmd>yunohost tools regen-conf {category} --dry-run --with-diff</cmd> and force the reset to the recommended configuration with <cmd>yunohost tools regen-conf {category} --force</cmd>",
"diagnosis_security_all_good": "No critical security vulnerability was found.",
"diagnosis_security_vulnerable_to_meltdown": "You appear vulnerable to the Meltdown criticial security vulnerability",
"diagnosis_security_vulnerable_to_meltdown_details": "To fix this, you should upgrade your system and reboot to load the new linux kernel (or contact your server provider if this doesn't work). See https://meltdownattack.com/ for more infos.",
@ -203,18 +228,25 @@
"diagnosis_description_mail": "Email",
"diagnosis_description_regenconf": "System configurations",
"diagnosis_description_security": "Security checks",
"diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside. Error: {error}",
"diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside in IPv{ipversion}.",
"diagnosis_ports_could_not_diagnose_details": "Error: {error}",
"diagnosis_ports_unreachable": "Port {port} is not reachable from outside.",
"diagnosis_ports_partially_unreachable": "Port {port} is not reachable from outside in IPv{failed}.",
"diagnosis_ports_ok": "Port {port} is reachable from outside.",
"diagnosis_ports_needed_by": "Exposing this port is needed for {1} features (service {0})",
"diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in https://yunohost.org/isp_box_config",
"diagnosis_http_could_not_diagnose": "Could not diagnose if domain is reachable from outside. Error: {error}",
"diagnosis_ports_needed_by": "Exposing this port is needed for {category} features (service {service})",
"diagnosis_ports_forwarding_tip": "To fix this issue, you most probably need to configure port forwarding on your internet router as described in <a href='https://yunohost.org/isp_box_config'>https://yunohost.org/isp_box_config</a>",
"diagnosis_http_hairpinning_issue": "Your local network does not seem to have hairpinning enabled.",
"diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?). You may be able to improve the situation by having a look at <a href='https://yunohost.org/dns_local_network'>https://yunohost.org/dns_local_network</a>",
"diagnosis_http_could_not_diagnose": "Could not diagnose if domains are reachable from outside in IPv{ipversion}.",
"diagnosis_http_could_not_diagnose_details": "Error: {error}",
"diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.",
"diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable. You should check that you're correctly forwarding port 80, that nginx is running, and that a firewall is not interfering.",
"diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.<br>1. The most common cause for this issue is that port 80 (and 443) <a href='https://yunohost.org/isp_box_config'>are not correctly forwarded to your server</a>.<br>2. You should also make sure that the service nginx is running<br>3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.",
"diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.",
"diagnosis_http_unknown_error": "An error happened while trying to reach your domain, it's very likely unreachable.",
"diagnosis_http_bad_status_code": "The diagnosis system could not reach your server. It might be that another machine answered instead of your server. You should check that you're correctly forwarding port 80, that your nginx configuration is up to date, and that a reverse-proxy is not interfering.",
"diagnosis_http_bad_status_code": "It looks like another machine (maybe your internet router) answered instead of your server.<br>1. The most common cause for this issue is that port 80 (and 443) <a href='https://yunohost.org/isp_box_config'>are not correctly forwarded to your server</a>.<br>2. On more complex setups: make sure that no firewall or reverse-proxy is interfering.",
"diagnosis_http_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network.",
"diagnosis_http_partially_unreachable": "Domain {domain} appears unreachable through HTTP from outside the local network in IPv{failed}, though it works in IPv{passed}.",
"diagnosis_http_nginx_conf_not_up_to_date": "This domain's nginx configuration appears to have been modified manually, and prevents YunoHost from diagnosing if it's reachable on HTTP.",
"diagnosis_http_nginx_conf_not_up_to_date_details": "To fix the situation, inspect the difference with the command line using <cmd>yunohost tools regen-conf nginx --dry-run --with-diff</cmd> and if you're ok, apply the changes with <cmd>yunohost tools regen-conf nginx --force</cmd>.",
"diagnosis_unknown_categories": "The following categories are unknown: {categories}",
"diagnosis_never_ran_yet": "It looks like this server was setup recently and there's no diagnosis report to show yet. You should start by running a full diagnosis, either from the webadmin or using 'yunohost diagnosis run' from the command line.",
"domain_cannot_remove_main": "You cannot remove '{domain:s}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n <another-domain>'; here is the list of candidate domains: {other_domains:s}",
@ -280,6 +312,7 @@
"global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)",
"global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', 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_smtp_allow_ipv6": "Allow the use of IPv6 to receive and send mail",
"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_user_password": "You are now about to define a new user 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 a variation of characters (uppercase, lowercase, digits and special characters).",
@ -463,7 +496,7 @@
"pattern_username": "Must be lower-case alphanumeric and underscore characters only",
"pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}",
"permission_already_allowed": "Group '{group}' already has permission '{permission}' enabled",
"permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled'",
"permission_already_disallowed": "Group '{group}' already has permission '{permission}' disabled",
"permission_already_exist": "Permission '{permission}' already exists",
"permission_already_up_to_date": "The permission was not updated because the addition/removal requests already match the current state.",
"permission_cannot_remove_main": "Removing a main permission is not allowed",

View file

@ -504,7 +504,7 @@
"apps_catalog_obsolete_cache": "La kaŝmemoro de la katalogo de programoj estas malplena aŭ malaktuala.",
"apps_catalog_update_success": "La aplika katalogo estis ĝisdatigita!",
"diagnosis_basesystem_kernel": "Servilo funkcias Linuksan kernon {kernel_version}",
"diagnosis_basesystem_ynh_single_version": "{0} versio: {1} ({2})",
"diagnosis_basesystem_ynh_single_version": "{package} versio: {version} ({repo})",
"diagnosis_basesystem_ynh_main_version": "Servilo funkcias YunoHost {main_version} ({repo})",
"diagnosis_basesystem_ynh_inconsistent_versions": "Vi prizorgas malkonsekvencajn versiojn de la YunoHost-pakoj... plej probable pro malsukcesa aŭ parta ĝisdatigo.",
"diagnosis_display_tip_web": "Vi povas iri al la sekcio Diagnozo (en la hejmekrano) por vidi la trovitajn problemojn.",
@ -513,9 +513,9 @@
"diagnosis_display_tip_cli": "Vi povas aranĝi 'yunohost diagnosis show --issues' por aperigi la trovitajn problemojn.",
"diagnosis_failed_for_category": "Diagnozo malsukcesis por kategorio '{category}': {error}",
"app_upgrade_script_failed": "Eraro okazis en la skripto pri ĝisdatiga programo",
"diagnosis_diskusage_verylow": "Stokado {mountpoint} (sur aparato {device)) restas nur {free_abs_GB} GB ({free_percent}%) spaco. Vi vere konsideru purigi iom da spaco.",
"diagnosis_ram_verylow": "La sistemo nur restas {available_abs_MB} MB ({available_percent}%) RAM! (el {total_abs_MB} MB)",
"diagnosis_mail_ougoing_port_25_blocked": "Eliranta haveno 25 ŝajnas esti blokita. Vi devas provi malŝlosi ĝin en via agorda panelo de provizanto (aŭ gastiganto). Dume la servilo ne povos sendi retpoŝtojn al aliaj serviloj.",
"diagnosis_diskusage_verylow": "Stokado {mountpoint} (sur aparato {device)) restas nur {free} ({free_percent}%) spaco. Vi vere konsideru purigi iom da spaco.",
"diagnosis_ram_verylow": "La sistemo nur restas {available} ({available_percent}%) RAM! (el {total})",
"diagnosis_mail_outgoing_port_25_blocked": "Eliranta haveno 25 ŝajnas esti blokita. Vi devas provi malŝlosi ĝin en via agorda panelo de provizanto (aŭ gastiganto). Dume la servilo ne povos sendi retpoŝtojn al aliaj serviloj.",
"diagnosis_http_bad_status_code": "Ne povis atingi vian servilon kiel atendite, ĝi redonis malbonan statuskodon. Povas esti, ke alia maŝino respondis anstataŭ via servilo. Vi devus kontroli, ke vi ĝuste redonas la havenon 80, ke via nginx-agordo ĝisdatigas kaj ke reverso-prokuro ne interbatalas.",
"main_domain_changed": "La ĉefa domajno estis ŝanĝita",
"yunohost_postinstall_end_tip": "La post-instalado finiĝis! Por fini vian agordon, bonvolu konsideri:\n - aldonado de unua uzanto tra la sekcio 'Uzantoj' de la retadreso (aŭ 'yunohost user create <username>' en komandlinio);\n - diagnozi problemojn atendantajn solvi por ke via servilo funkciu kiel eble plej glate tra la sekcio 'Diagnosis' de la retadministrado (aŭ 'yunohost diagnosis run' en komandlinio);\n - legante la partojn 'Finigi vian agordon' kaj 'Ekkoni Yunohost' en la administra dokumentado: https://yunohost.org/admindoc.",
@ -530,9 +530,9 @@
"diagnosis_ip_weird_resolvconf_details": "Anstataŭe, ĉi tiu dosiero estu ligilo kun /etc/resolvconf/run/resolv.conf mem montrante al 127.0.0.1 (dnsmasq). La efektivaj solvantoj devas agordi per /etc/resolv.dnsmasq.conf.",
"diagnosis_dns_good_conf": "Bona DNS-agordo por domajno {domain} (kategorio {category})",
"diagnosis_dns_bad_conf": "Malbona / mankas DNS-agordo por domajno {domain} (kategorio {category})",
"diagnosis_ram_ok": "La sistemo ankoraŭ havas {available_abs_MB} MB ({available_percent}%) RAM forlasita de {total_abs_MB} MB.",
"diagnosis_swap_none": "La sistemo tute ne havas interŝanĝon. Vi devus pripensi aldoni almenaŭ 256 MB da interŝanĝo por eviti situaciojn en kiuj la sistemo restas sen memoro.",
"diagnosis_swap_notsomuch": "La sistemo havas nur {total_MB} MB-interŝanĝon. Vi konsideru havi almenaŭ 256 MB por eviti situaciojn en kiuj la sistemo restas sen memoro.",
"diagnosis_ram_ok": "La sistemo ankoraŭ havas {available} ({available_percent}%) RAM forlasita de {total}.",
"diagnosis_swap_none": "La sistemo tute ne havas interŝanĝon. Vi devus pripensi aldoni almenaŭ {recommended} da interŝanĝo por eviti situaciojn en kiuj la sistemo restas sen memoro.",
"diagnosis_swap_notsomuch": "La sistemo havas nur {total}-interŝanĝon. Vi konsideru havi almenaŭ {recommended} por eviti situaciojn en kiuj la sistemo restas sen memoro.",
"diagnosis_regenconf_manually_modified_details": "Ĉi tio probable estas bona tiel longe kiel vi scias kion vi faras;)!",
"diagnosis_regenconf_manually_modified_debian": "Agordodosiero {file} estis modifita permane kompare kun la defaŭlta Debian.",
"diagnosis_regenconf_manually_modified_debian_details": "Ĉi tio probable estas bona, sed devas observi ĝin...",
@ -541,12 +541,12 @@
"diagnosis_no_cache": "Neniu diagnoza kaŝmemoro por kategorio '{category}'",
"diagnosis_ip_broken_dnsresolution": "Rezolucio pri domajna nomo rompiĝas pro iu kialo ... Ĉu fajroŝirmilo blokas DNS-petojn ?",
"diagnosis_ip_broken_resolvconf": "Rezolucio pri domajna nomo ŝajnas esti rompita en via servilo, kiu ŝajnas rilata al /etc/resolv.conf ne notante 127.0.0.1.",
"diagnosis_dns_missing_record": "Laŭ la rekomendita DNS-agordo, vi devas aldoni DNS-registron kun tipo {0}, nomo {1} kaj valoro {2}. Vi povas kontroli https://yunohost.org/dns_config por pliaj informoj.",
"diagnosis_dns_discrepancy": "La DNS-registro kun tipo {0} kaj nomo {1} ne kongruas kun la rekomendita agordo. Nuna valoro: {2}. Esceptita valoro: {3}. Vi povas kontroli https://yunohost.org/dns_config por pliaj informoj.",
"diagnosis_dns_missing_record": "Laŭ la rekomendita DNS-agordo, vi devas aldoni DNS-registron kun\ntipo: {type}\nnomo: {name}\nvaloro: {value}",
"diagnosis_dns_discrepancy": "La DNS-registro kun tipo {type} kaj nomo {name} ne kongruas kun la rekomendita agordo.\nNuna valoro: {current}\nEsceptita valoro: {value}",
"diagnosis_services_conf_broken": "Agordo estas rompita por servo {service} !",
"diagnosis_services_bad_status": "Servo {service} estas {status} :(",
"diagnosis_ram_low": "La sistemo havas {available_abs_MB} MB ({available_percent}%) RAM forlasita de {total_abs_MB} MB. Estu zorgema.",
"diagnosis_swap_ok": "La sistemo havas {total_MB} MB da interŝanĝoj!",
"diagnosis_ram_low": "La sistemo havas {available} ({available_percent}%) RAM forlasita de {total}. Estu zorgema.",
"diagnosis_swap_ok": "La sistemo havas {total} da interŝanĝoj!",
"diagnosis_mail_ougoing_port_25_ok": "Eliranta haveno 25 ne estas blokita kaj retpoŝto povas esti sendita al aliaj serviloj.",
"diagnosis_regenconf_allgood": "Ĉiuj agordaj dosieroj kongruas kun la rekomendita agordo!",
"diagnosis_regenconf_manually_modified": "Agordodosiero {file} estis permane modifita.",
@ -555,8 +555,9 @@
"diagnosis_description_services": "Servo kontrolas staton",
"diagnosis_description_systemresources": "Rimedaj sistemoj",
"diagnosis_description_security": "Sekurecaj kontroloj",
"diagnosis_ports_could_not_diagnose": "Ne povis diagnozi, ĉu haveblaj havenoj de ekstere. Eraro: {error}",
"diagnosis_services_bad_status_tip": "Vi povas provi rekomenci la servon, kaj se ĝi ne funkcias, trarigardu la servajn protokolojn uzante 'yunohost service log {0}' aŭ tra la sekcio 'Servoj' de la retadreso.",
"diagnosis_ports_could_not_diagnose": "Ne povis diagnozi, ĉu haveblaj havenoj de ekstere.",
"diagnosis_ports_could_not_diagnose_details": "Eraro: {error}",
"diagnosis_services_bad_status_tip": "Vi povas provi rekomenci la servon, kaj se ĝi ne funkcias, trarigardu la servajn protokolojn uzante 'yunohost service log {service}' aŭ tra la sekcio 'Servoj' de la retadreso.",
"diagnosis_security_vulnerable_to_meltdown_details": "Por ripari tion, vi devas ĝisdatigi vian sistemon kaj rekomenci por ŝarĝi la novan linux-kernon (aŭ kontaktu vian servilan provizanton se ĉi tio ne funkcias). Vidu https://meltdownattack.com/ por pliaj informoj.",
"diagnosis_description_basesystem": "Baza sistemo",
"diagnosis_description_regenconf": "Sistemaj agordoj",
@ -564,21 +565,21 @@
"log_domain_main_domain": "Faru '{}' kiel ĉefa domajno",
"diagnosis_http_timeout": "Tempolimigita dum provado kontakti vian servilon de ekstere. Ĝi ŝajnas esti neatingebla. Vi devus kontroli, ke vi ĝuste redonas la havenon 80, ke nginx funkcias kaj ke fajroŝirmilo ne interbatalas.",
"diagnosis_http_connection_error": "Rilata eraro: ne povis konektiĝi al la petita domajno, tre probable ĝi estas neatingebla.",
"diagnosis_http_unknown_error": "Eraro okazis dum provado atingi vian domajnon, tre probable ĝi estas neatingebla.",
"migration_description_0013_futureproof_apps_catalog_system": "Migru al la nova katalogosistemo pri estontecaj programoj",
"diagnosis_ignored_issues": "(+ {nb_ignored} ignorataj aferoj))",
"diagnosis_found_errors": "Trovis {errors} signifa(j) afero(j) rilata al {category}!",
"diagnosis_found_errors_and_warnings": "Trovis {errors} signifaj problemo (j) (kaj {warnings} averto) rilataj al {category}!",
"diagnosis_diskusage_low": "Stokado {mountpoint} (sur aparato {device)) restas nur {free_abs_GB} GB ({free_percent}%) spaco. Estu zorgema.",
"diagnosis_diskusage_ok": "Stokado {mountpoint} (sur aparato {device) ankoraŭ restas {free_abs_GB} GB ({free_percent}%) spaco!",
"diagnosis_diskusage_low": "Stokado {mountpoint} (sur aparato {device)) restas nur {free} ({free_percent}%) spaco. Estu zorgema.",
"diagnosis_diskusage_ok": "Stokado {mountpoint} (sur aparato {device) ankoraŭ restas {free} ({free_percent}%) spaco!",
"global_settings_setting_pop3_enabled": "Ebligu la protokolon POP3 por la poŝta servilo",
"diagnosis_unknown_categories": "La jenaj kategorioj estas nekonataj: {categories}",
"diagnosis_services_running": "Servo {service} funkcias!",
"diagnosis_ports_unreachable": "Haveno {port} ne atingeblas de ekstere.",
"diagnosis_ports_ok": "Haveno {port} atingeblas de ekstere.",
"diagnosis_ports_needed_by": "Eksponi ĉi tiun havenon necesas por servo {0}",
"diagnosis_ports_needed_by": "Eksponi ĉi tiun havenon necesas por servo {service}",
"diagnosis_ports_forwarding_tip": "Por solvi ĉi tiun problemon, plej probable vi devas agordi la plusendon de haveno en via interreta enkursigilo kiel priskribite en https://yunohost.org/isp_box_config",
"diagnosis_http_could_not_diagnose": "Ne povis diagnozi, ĉu atingeblas domajno de ekstere. Eraro: {error}",
"diagnosis_http_could_not_diagnose": "Ne povis diagnozi, ĉu atingeblas domajno de ekstere.",
"diagnosis_http_could_not_diagnose_details": "Eraro: {error}",
"diagnosis_http_ok": "Domajno {domain} atingeblas de ekstere.",
"diagnosis_http_unreachable": "Domajno {domain} estas atingebla per HTTP de ekstere.",
"domain_cannot_remove_main_add_new_one": "Vi ne povas forigi '{domain:s}' ĉar ĝi estas la ĉefa domajno kaj via sola domajno, vi devas unue aldoni alian domajnon uzante ''yunohost domain add <another-domain.com>', tiam agordi kiel ĉefan domajnon uzante 'yunohost domain main-domain -n <another-domain.com>' kaj tiam vi povas forigi la domajnon' {domain:s} 'uzante' yunohost domain remove {domain:s} '.'",

View file

@ -505,7 +505,7 @@
"app_remove_after_failed_install": "Eliminando la aplicación tras el fallo de instalación…",
"diagnosis_basesystem_host": "El servidor está ejecutando Debian {debian_version}.",
"diagnosis_basesystem_kernel": "El servidor está ejecutando el núcleo de Linux {kernel_version}",
"diagnosis_basesystem_ynh_single_version": "{0} versión: {1} ({2})",
"diagnosis_basesystem_ynh_single_version": "{package} versión: {version} ({repo})",
"diagnosis_basesystem_ynh_main_version": "El servidor está ejecutando YunoHost {main_version} ({repo})",
"diagnosis_basesystem_ynh_inconsistent_versions": "Está ejecutando versiones incoherentes de los paquetes de YunoHost... probablemente por una actualización errónea o parcial.",
"diagnosis_failed_for_category": "Diagnóstico fallido para la categoría «{category}» : {error}",
@ -528,9 +528,9 @@
"diagnosis_ip_no_ipv4": "El servidor no cuenta con ipv4 funcional.",
"diagnosis_ip_not_connected_at_all": "¿¡Está conectado el servidor a internet!?",
"diagnosis_ip_broken_resolvconf": "DNS parece no funcionar en tu servidor, lo que parece estar relacionado con /etc/resolv.conf no apuntando a 127.0.0.1.",
"diagnosis_dns_missing_record": "Según la configuración DNS recomendada, deberías añadir un registro DNS de tipo {0}, nombre {1} y valor {2}. Puedes consultar https://yunohost.org/dns_config para más información.",
"diagnosis_diskusage_low": "El almacenamiento {mountpoint} (en dispositivo {device}) solo tiene {free_abs_GB} GB ({free_percent}%) de espacio disponible. Ten cuidado.",
"diagnosis_services_bad_status_tip": "Puedes intentar reiniciar el servicio, y si no funciona, echar un vistazo a los logs del servicio usando 'yunohost service log {0}' o a través de la sección 'Servicios' en webadmin.",
"diagnosis_dns_missing_record": "Según la configuración DNS recomendada, deberías añadir un registro DNS\ntipo: {type}\nnombre: {name}\nvalor: {value}",
"diagnosis_diskusage_low": "El almacenamiento {mountpoint} (en dispositivo {device}) solo tiene {free} ({free_percent}%) de espacio disponible. Ten cuidado.",
"diagnosis_services_bad_status_tip": "Puedes intentar reiniciar el servicio, y si no funciona, echar un vistazo a los logs del servicio usando 'yunohost service log {service}' o a través de la sección 'Servicios' en webadmin.",
"diagnosis_ip_connected_ipv6": "¡El servidor está conectado a internet a través de IPv6!",
"diagnosis_ip_no_ipv6": "El servidor no cuenta con IPv6 funcional.",
"diagnosis_ip_dnsresolution_working": "¡DNS no está funcionando!",
@ -539,22 +539,22 @@
"diagnosis_ip_weird_resolvconf_details": "En su lugar, este fichero debería ser un enlace simbólico a /etc/resolvconf/run/resolv.conf apuntando a 127.0.0.1 (dnsmasq). Los servidores de nombre de domino deben configurarse a través de /etc/resolv.dnsmasq.conf.",
"diagnosis_dns_good_conf": "Buena configuración DNS para el dominio {domain} (categoría {category})",
"diagnosis_dns_bad_conf": "Configuración mala o faltante de los DNS para el dominio {domain} (categoría {category})",
"diagnosis_dns_discrepancy": "El registro DNS con tipo {0} y nombre {1} no se corresponde a la configuración recomendada. Valor actual: {2}. Valor esperado: {3}. Puedes consultar https://yunohost.org/dns_config para más información.",
"diagnosis_dns_discrepancy": "El registro DNS con tipo {type} y nombre {name} no se corresponde a la configuración recomendada.\nValor actual: {current}\nValor esperado: {value}",
"diagnosis_services_bad_status": "El servicio {service} está {status} :(",
"diagnosis_diskusage_verylow": "El almacenamiento {mountpoint} (en el dispositivo {device}) sólo tiene {free_abs_GB} GB ({free_percent}%) de espacio disponible. Deberías considerar la posibilidad de limpiar algo de espacio.",
"diagnosis_diskusage_ok": "¡El almacenamiento {mountpoint} (en el dispositivo {device}) todavía tiene {free_abs_GB} GB ({free_percent}%) de espacio libre!",
"diagnosis_diskusage_verylow": "El almacenamiento {mountpoint} (en el dispositivo {device}) sólo tiene {free} ({free_percent}%) de espacio disponible. Deberías considerar la posibilidad de limpiar algo de espacio.",
"diagnosis_diskusage_ok": "¡El almacenamiento {mountpoint} (en el dispositivo {device}) todavía tiene {free} ({free_percent}%) de espacio libre!",
"diagnosis_services_conf_broken": "¡Mala configuración para el servicio {service}!",
"diagnosis_services_running": "¡El servicio {service} está en ejecución!",
"diagnosis_failed": "No se ha podido obtener el resultado del diagnóstico para la categoría '{category}': {error}",
"diagnosis_ip_connected_ipv4": "¡El servidor está conectado a internet a través de IPv4!",
"diagnosis_security_vulnerable_to_meltdown_details": "Para corregir esto, debieras actualizar y reiniciar tu sistema para cargar el nuevo kernel de Linux (o contacta tu proveedor si esto no funciona). Mas información en https://meltdownattack.com/",
"diagnosis_ram_verylow": "Al sistema le queda solamente {available_abs_MB} MB ({available_percent}%) de RAM! (De un total de {total_abs_MB} MB)",
"diagnosis_ram_low": "Al sistema le queda {available_abs_MB} MB ({available_percent}%) de RAM de un total de {total_abs_MB} MB. Cuidado.",
"diagnosis_ram_ok": "El sistema aun tiene {available_abs_MB} MB ({available_percent}%) de RAM de un total de {total_abs_MB} MB.",
"diagnosis_swap_none": "El sistema no tiene mas espacio de intercambio. Considera agregar por lo menos 256 MB de espacio de intercambio para evitar que el sistema se quede sin memoria.",
"diagnosis_swap_notsomuch": "Al sistema le queda solamente {total_MB} MB de espacio de intercambio. Considera agregar al menos 256 MB para evitar que el sistema se quede sin memoria.",
"diagnosis_ram_verylow": "Al sistema le queda solamente {available} ({available_percent}%) de RAM! (De un total de {total})",
"diagnosis_ram_low": "Al sistema le queda {available} ({available_percent}%) de RAM de un total de {total}. Cuidado.",
"diagnosis_ram_ok": "El sistema aun tiene {available} ({available_percent}%) de RAM de un total de {total}.",
"diagnosis_swap_none": "El sistema no tiene mas espacio de intercambio. Considera agregar por lo menos {recommended} de espacio de intercambio para evitar que el sistema se quede sin memoria.",
"diagnosis_swap_notsomuch": "Al sistema le queda solamente {total} de espacio de intercambio. Considera agregar al menos {recommended} para evitar que el sistema se quede sin memoria.",
"diagnosis_mail_ougoing_port_25_ok": "El puerto de salida 25 no esta bloqueado y los correos electrónicos pueden ser enviados a otros servidores.",
"diagnosis_mail_ougoing_port_25_blocked": "El puerto de salida 25 parece estar bloqueado. Intenta desbloquearlo con el panel de configuración de tu proveedor de servicios de Internet (o proveedor de halbergue). Mientras tanto, el servidor no podrá enviar correos electrónicos a otros servidores.",
"diagnosis_mail_outgoing_port_25_blocked": "El puerto de salida 25 parece estar bloqueado. Intenta desbloquearlo con el panel de configuración de tu proveedor de servicios de Internet (o proveedor de halbergue). Mientras tanto, el servidor no podrá enviar correos electrónicos a otros servidores.",
"diagnosis_regenconf_allgood": "Todos los archivos de configuración están en linea con la configuración recomendada!",
"diagnosis_regenconf_manually_modified": "El archivo de configuración {file} fue modificado manualmente.",
"diagnosis_regenconf_manually_modified_details": "Esto este probablemente BIEN siempre y cuando sepas lo que estas haciendo ;) !",
@ -568,11 +568,12 @@
"diagnosis_description_services": "Comprobación del estado de los servicios",
"diagnosis_description_ports": "Exposición de puertos",
"diagnosis_description_systemresources": "Recursos del sistema",
"diagnosis_swap_ok": "El sistema tiene {total_MB} MB de espacio de intercambio!",
"diagnosis_ports_needed_by": "La apertura de este puerto es requerida para la funcionalidad {1} (service {0})",
"diagnosis_swap_ok": "El sistema tiene {total} de espacio de intercambio!",
"diagnosis_ports_needed_by": "La apertura de este puerto es requerida para la funcionalidad {category} (service {service})",
"diagnosis_ports_ok": "El puerto {port} es accesible desde internet.",
"diagnosis_ports_unreachable": "El puerto {port} no es accesible desde internet.",
"diagnosis_ports_could_not_diagnose": "No se puede comprobar si los puertos están accesibles desde el exterior. Error: {error}",
"diagnosis_ports_could_not_diagnose": "No se puede comprobar si los puertos están accesibles desde el exterior.",
"diagnosis_ports_could_not_diagnose_details": "Error: {error}",
"diagnosis_description_security": "Validación de seguridad",
"diagnosis_description_regenconf": "Configuraciones de sistema",
"diagnosis_description_mail": "Correo electrónico",
@ -592,10 +593,10 @@
"diagnosis_unknown_categories": "Las siguientes categorías están desconocidas: {categories}",
"diagnosis_http_unreachable": "El dominio {domain} esta fuera de alcance desde internet y a través de HTTP.",
"diagnosis_http_bad_status_code": "El sistema de diagnostico no pudo comunicarse con su servidor. Puede ser otra maquina que contesto en lugar del servidor. Debería verificar en su firewall que el re-direccionamiento del puerto 80 esta correcto.",
"diagnosis_http_unknown_error": "Hubo un error durante la búsqueda de su dominio, parece inalcanzable.",
"diagnosis_http_connection_error": "Error de conexión: Ne se pudo conectar al dominio solicitado,",
"diagnosis_http_timeout": "El intento de contactar a su servidor desde internet corrió fuera de tiempo. Al parece esta incomunicado. Debería verificar que nginx corre en el puerto 80, y que la redireción del puerto 80 no interfiere con en el firewall.",
"diagnosis_http_ok": "El Dominio {domain} es accesible desde internet a través de HTTP.",
"diagnosis_http_could_not_diagnose": "No se pudo verificar si el dominio es accesible desde internet. Error: {error}",
"diagnosis_http_could_not_diagnose": "No se pudo verificar si el dominio es accesible desde internet.",
"diagnosis_http_could_not_diagnose_details": "Error: {error}",
"diagnosis_ports_forwarding_tip": "Para solucionar este incidente, debería configurar el \"port forwading\" en su router como especificado en https://yunohost.org/isp_box_config"
}

View file

@ -17,9 +17,9 @@
"app_removed": "{app:s} supprimé",
"app_requirements_checking": "Vérification des paquets requis pour {app} …",
"app_requirements_unmeet": "Les pré-requis de {app} ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}",
"app_sources_fetch_failed": "Impossible de récupérer les fichiers sources, l'URL est-elle correcte ?",
"app_sources_fetch_failed": "Impossible de récupérer les fichiers sources, lURL est-elle correcte ?",
"app_unknown": "Application inconnue",
"app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n'est pas supporté",
"app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application nest pas supporté",
"app_upgrade_failed": "Impossible de mettre à jour {app:s} : {error}",
"app_upgraded": "{app:s} mis à jour",
"ask_email": "Adresse de courriel",
@ -35,14 +35,14 @@
"backup_archive_open_failed": "Impossible douvrir larchive de la sauvegarde",
"backup_cleaning_failed": "Impossible de nettoyer le dossier temporaire de sauvegarde",
"backup_created": "Sauvegarde terminée",
"backup_creation_failed": "Impossible de créer l'archive de la sauvegarde",
"backup_creation_failed": "Impossible de créer larchive de la sauvegarde",
"backup_delete_error": "Impossible de supprimer '{path:s}'",
"backup_deleted": "La sauvegarde a été supprimée",
"backup_hook_unknown": "Script de sauvegarde '{hook:s}' inconnu",
"backup_invalid_archive": "Archive de sauvegarde invalide",
"backup_nothings_done": "Il ny a rien à sauvegarder",
"backup_output_directory_forbidden": "Dossier de destination interdit. Les sauvegardes ne peuvent être créées dans les sous-dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives",
"backup_output_directory_not_empty": "Le répertoire de destination n'est pas vide",
"backup_output_directory_not_empty": "Le répertoire de destination nest pas vide",
"backup_output_directory_required": "Vous devez spécifier un dossier de destination pour la sauvegarde",
"backup_running_hooks": "Exécution des scripts de sauvegarde …",
"custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application personnalisée {app:s}",
@ -63,7 +63,7 @@
"dyndns_cron_removed": "La tâche cron pour le domaine DynDNS enlevée",
"dyndns_ip_update_failed": "Impossible de mettre à jour ladresse IP sur le domaine DynDNS",
"dyndns_ip_updated": "Mise à jour de votre IP pour le domaine DynDNS",
"dyndns_key_generating": "Génération de la clé DNS ... , cela peut prendre un certain temps.",
"dyndns_key_generating": "Génération de la clé DNS , cela peut prendre un certain temps.",
"dyndns_key_not_found": "Clé DNS introuvable pour le domaine",
"dyndns_no_domain_registered": "Aucun domaine enregistré avec DynDNS",
"dyndns_registered": "Domaine DynDNS enregistré",
@ -75,18 +75,18 @@
"field_invalid": "Champ incorrect : '{:s}'",
"firewall_reload_failed": "Impossible de recharger le pare-feu",
"firewall_reloaded": "Pare-feu rechargé",
"firewall_rules_cmd_failed": "Certaines règles du pare-feu nont pas pu être appliquées. Plus d'info dans le journal de log.",
"firewall_rules_cmd_failed": "Certaines règles du pare-feu nont pas pu être appliquées. Plus dinfo dans le journal de log.",
"hook_exec_failed": "Échec de lexécution du script : {path:s}",
"hook_exec_not_terminated": "Lexécution du script {path:s} ne sest pas terminée correctement",
"hook_list_by_invalid": "Propriété invalide pour lister les actions par celle-ci",
"hook_name_unknown": "Nom de l'action '{name:s}' inconnu",
"hook_name_unknown": "Nom de laction '{name:s}' inconnu",
"installation_complete": "Installation terminée",
"installation_failed": "Quelque chose s'est mal passé lors de l'installation",
"installation_failed": "Quelque chose sest mal passé lors de linstallation",
"ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge",
"iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge",
"ldap_initialized": "Lannuaire LDAP initialisé",
"mail_alias_remove_failed": "Impossible de supprimer lalias de courriel '{mail:s}'",
"mail_domain_unknown": "Le domaine '{domain:s}' de cette adress de courriel n'est pas valide. Merci d'utiliser un domain administré par ce serveur.",
"mail_domain_unknown": "Le domaine '{domain:s}' de cette adresse de courriel nest pas valide. Merci dutiliser un domaine administré par ce serveur.",
"mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'",
"main_domain_change_failed": "Impossible de modifier le domaine principal",
"main_domain_changed": "Le domaine principal modifié",
@ -112,13 +112,13 @@
"restore_complete": "Restauré",
"restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]",
"restore_failed": "Impossible de restaurer le système",
"restore_hook_unavailable": "Le script de restauration '{part:s}' nest pas disponible sur votre système, et ne l'est pas non plus dans larchive",
"restore_hook_unavailable": "Le script de restauration '{part:s}' nest pas disponible sur votre système, et ne lest pas non plus dans larchive",
"restore_nothings_done": "Rien na été restauré",
"restore_running_app_script": "Exécution du script de restauration de l'application '{app:s}' .…",
"restore_running_app_script": "Exécution du script de restauration de lapplication '{app:s}' …",
"restore_running_hooks": "Exécution des scripts de restauration …",
"service_add_failed": "Impossible dajouter le service '{service:s}'",
"service_added": "Le service '{service:s}' a été ajouté",
"service_already_started": "Le service '{service:s}' est déjà en cours d'exécution",
"service_already_started": "Le service '{service:s}' est déjà en cours dexécution",
"service_already_stopped": "Le service '{service:s}' est déjà arrêté",
"service_cmd_exec_failed": "Impossible dexécuter la commande '{command:s}'",
"service_disable_failed": "Impossible de ne pas lancer le service « {service:s} » au démarrage.\n\nJournaux récents du service : {logs:s}",
@ -152,46 +152,46 @@
"user_deleted": "Lutilisateur supprimé",
"user_deletion_failed": "Impossible de supprimer lutilisateur {user}: {error}",
"user_home_creation_failed": "Impossible de créer le dossier personnel de lutilisateur",
"user_unknown": "L'utilisateur {user:s} est inconnu",
"user_update_failed": "Impossible de mettre à jour l'utilisateur {user}: {error}",
"user_unknown": "Lutilisateur {user:s} est inconnu",
"user_update_failed": "Impossible de mettre à jour lutilisateur {user}: {error}",
"user_updated": "Lutilisateur a été modifié",
"yunohost_already_installed": "YunoHost est déjà installé",
"yunohost_ca_creation_failed": "Impossible de créer lautorité de certification",
"yunohost_configured": "YunoHost est maintenant configuré",
"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_installing": "Linstallation de YunoHost est en cours …",
"yunohost_not_installed": "YunoHost nest 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_domain_unknown": "Domaine {domain:s} inconnu",
"certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} nest pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force pour cela)",
"certmanager_certificate_fetching_or_enabling_failed": "Il semble que lactivation du nouveau certificat pour {domain:s} a échoué …",
"certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain:s} nest pas émis par Lets Encrypt. Impossible de le renouveler automatiquement !",
"certmanager_attempt_to_renew_valid_cert": "Le certificat pour le domaine {domain:s} n'est pas sur le point dexpirer ! (Vous pouvez utiliser --force si vous savez ce que vous faites)",
"certmanager_attempt_to_renew_valid_cert": "Le certificat pour le domaine {domain:s} nest pas sur le point dexpirer ! (Vous pouvez utiliser --force si vous savez ce que vous faites)",
"certmanager_domain_http_not_working": "Il semble que le domaine {domain:s} ne soit pas accessible via HTTP. Veuillez vérifier que vos configuration DNS et Nginx sont correctes",
"certmanager_error_no_A_record": "Aucun enregistrement DNS 'A' na été trouvé pour {domain:s}. Vous devez faire pointer votre nom de domaine vers votre machine pour être en mesure dinstaller un certificat Lets Encrypt ! (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)",
"certmanager_domain_dns_ip_differs_from_public_ip": "Lenregistrement DNS 'A' du domaine {domain:s} est différent de ladresse IP de ce serveur. Si vous avez récemment modifié votre enregistrement 'A', veuillez attendre sa propagation (quelques vérificateur de propagation DNS sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)",
"certmanager_domain_dns_ip_differs_from_public_ip": "Lenregistrement DNS 'A' du domaine {domain:s} est différent de ladresse IP de ce serveur. Si vous avez récemment modifié votre enregistrement 'A', veuillez attendre sa propagation (quelques vérificateurs de propagation DNS sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)",
"certmanager_cannot_read_cert": "Quelque chose sest mal passé lors de la tentative douverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), la cause est : {reason:s}",
"certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »",
"certmanager_cert_install_success": "Le certificat Lets Encrypt est maintenant installé pour le domaine « {domain:s} »",
"certmanager_cert_renew_success": "Certificat Let's Encrypt renouvelé pour le domaine '{domain:s}'",
"certmanager_cert_renew_success": "Certificat Lets Encrypt renouvelé pour le domaine '{domain:s}'",
"certmanager_cert_signing_failed": "Impossible de signer le nouveau certificat",
"certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain:s} (fichier : {file:s})",
"certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration NGINX {filepath:s} est en conflit et doit être préalablement retiré",
"certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations",
"ldap_init_failed_to_create_admin": "Linitialisation de l'annuaire LDAP na pas réussi à créer lutilisateur admin",
"domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal. Vous devez d'abord définir un autre domaine comme domaine principal à l'aide de 'yunohost domain main-domain -n <another-domain>', voici la liste des domaines candidats. : {other_domains:s}",
"ldap_init_failed_to_create_admin": "Linitialisation de lannuaire LDAP na pas réussi à créer lutilisateur admin",
"domain_cannot_remove_main": "Vous ne pouvez pas supprimer '{domain:s}' car il sagit du domaine principal. Vous devez dabord définir un autre domaine comme domaine principal à laide de 'yunohost domain main-domain -n <another-domain>', voici la liste des domaines candidats. : {other_domains:s}",
"certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour lautorité du certificat auto-signé est introuvable (fichier : {file:s})",
"certmanager_unable_to_parse_self_CA_name": "Impossible danalyser le nom de lautorité du certificat auto-signé (fichier : {file:s})",
"mailbox_used_space_dovecot_down": "Le service de courriel Dovecot doit être démarré si vous souhaitez voir lespace disque occupé par la messagerie",
"domains_available": "Domaines disponibles :",
"backup_archive_broken_link": "Impossible daccéder à larchive de sauvegarde (lien invalide vers {path:s})",
"certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez d'abord exécuter cert-install.",
"certmanager_http_check_timeout": "Expiration du délai lorsque le serveur a essayé de se contacter lui-même via HTTP en utilisant l'adresse IP public {ip:s} du domaine {domain:s}. Vous rencontrez peut-être un problème dhairpinning ou alors le pare-feu/routeur en amont de votre serveur est mal configuré.",
"certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez dabord exécuter cert-install.",
"certmanager_http_check_timeout": "Expiration du délai lorsque le serveur a essayé de se contacter lui-même via HTTP en utilisant ladresse IP public {ip:s} du domaine {domain:s}. Vous rencontrez peut-être un problème dhairpinning ou alors le pare-feu/routeur en amont de votre serveur est mal configuré.",
"certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Lets Encrypt. Linstallation ou le renouvellement du certificat a été annulé. Veuillez réessayer plus tard.",
"domain_hostname_failed": "Échec de lutilisation dun nouveau nom dhôte. Cela pourrait causer des soucis plus tard (peut-être que ça nen causera pas).",
"yunohost_ca_creation_success": "Lautorité de certification locale créée.",
"app_already_installed_cant_change_url": "Cette application est déjà installée. LURL ne peut pas être changé simplement par cette fonction. Vérifiez si cela est disponible avec `app changeurl`.",
"app_change_url_failed_nginx_reload": "Le redémarrage de Nginx a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}",
"app_change_url_identical_domains": "Lancien et le nouveau couple domaine/chemin_de_l'URL sont identiques pour ('{domain:s}{path:s}'), rien à faire.",
"app_change_url_identical_domains": "Lancien et le nouveau couple domaine/chemin_de_lURL sont identiques pour ('{domain:s}{path:s}'), rien à faire.",
"app_change_url_no_script": "Lapplication '{app_name:s}' ne prend pas encore en charge le changement dURL. Vous devriez peut-être la mettre à jour.",
"app_change_url_success": "LURL de lapplication {app:s} a été changée en {domain:s}{path:s}",
"app_location_unavailable": "Cette URL nest pas disponible ou est en conflit avec une application existante :\n{apps:s}",
@ -206,7 +206,7 @@
"global_settings_setting_example_int": "Exemple doption de type entier",
"global_settings_setting_example_string": "Exemple doption de type chaîne",
"global_settings_setting_example_enum": "Exemple doption de type énumération",
"global_settings_unknown_type": "Situation inattendue : la configuration {setting:s} semble avoir le type {unknown_type:s} mais celui-ci n'est pas pris en charge par le système.",
"global_settings_unknown_type": "Situation inattendue : la configuration {setting:s} semble avoir le type {unknown_type:s} mais celui-ci nest pas pris en charge par le système.",
"global_settings_unknown_setting_from_settings_file": "Clé inconnue dans les paramètres : '{setting_key:s}', rejet de cette clé et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json",
"backup_abstract_method": "Cette méthode de sauvegarde reste à implémenter",
"backup_applying_method_tar": "Création de larchive TAR de la sauvegarde …",
@ -214,8 +214,8 @@
"backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans le répertoire borg-backup …",
"backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method:s}' …",
"backup_archive_system_part_not_available": "La partie '{part:s}' du système nest pas disponible dans cette sauvegarde",
"backup_archive_writing_error": "Impossible d'ajouter des fichiers '{source:s}' (nommés dans l'archive : '{dest:s}') à sauvegarder dans l'archive compressée '{archive:s}'",
"backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {size:s} temporairement? (Cette méthode est utilisée car certains fichiers n'ont pas pu être préparés avec une méthode plus efficace.)",
"backup_archive_writing_error": "Impossible dajouter des fichiers '{source:s}' (nommés dans larchive : '{dest:s}') à sauvegarder dans larchive compressée '{archive:s}'",
"backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {size:s} temporairement ? (Cette méthode est utilisée car certains fichiers nont pas pu être préparés avec une méthode plus efficace.)",
"backup_borg_not_implemented": "La méthode de sauvegarde Borg nest pas encore implémentée",
"backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de larchive décompressée",
"backup_copying_to_organize_the_archive": "Copie de {size:s} Mo pour organiser larchive",
@ -235,26 +235,26 @@
"global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason:s}",
"restore_removing_tmp_dir_failed": "Impossible de sauvegarder un ancien dossier temporaire",
"restore_extracting": "Extraction des fichiers nécessaires depuis larchive …",
"restore_may_be_not_enough_disk_space": "Votre système semble ne pas avoir suffisamment despace disponible (L'espace libre est de {free_space:d} octets. Le besoin d'espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d'espace nécessaire est de {margin:d} octets)",
"restore_not_enough_disk_space": "Espace disponible insuffisant (L'espace libre est de {free_space:d} octets. Le besoin d'espace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité d'espace nécessaire est de {margin:d} octets)",
"restore_may_be_not_enough_disk_space": "Votre système semble ne pas avoir suffisamment despace disponible (Lespace libre est de {free_space:d} octets. Le besoin despace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité despace nécessaire est de {margin:d} octets)",
"restore_not_enough_disk_space": "Espace disponible insuffisant (Lespace libre est de {free_space:d} octets. Le besoin despace nécessaire est de {needed_space:d} octets. En appliquant une marge de sécurité, la quantité despace nécessaire est de {margin:d} octets)",
"restore_system_part_failed": "Impossible de restaurer la partie '{part:s}' du système",
"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.",
"migrations_cant_reach_migration_file": "Impossible d'accéder aux fichiers de migration via le chemin '%s'",
"migrations_cant_reach_migration_file": "Impossible daccéder aux fichiers de migration via le chemin '%s'",
"migrations_loading_migration": "Chargement de la migration {id} …",
"migrations_migration_has_failed": "La migration {id} a échoué avec lexception {exception} : annulation",
"migrations_no_migrations_to_run": "Aucune migration à lancer",
"migrations_skip_migration": "Ignorer et passer la migration {id} …",
"server_shutdown": "Le serveur va é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_reboot": "Le serveur va redémarrer",
"server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers:s}]",
"app_upgrade_some_app_failed": "Certaines applications nont pas été mises à jour",
"dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.",
"dyndns_domain_not_provided": "Le fournisseur DynDNS {provider:s} ne peut pas fournir le domaine {domain:s}.",
"app_make_default_location_already_used": "Impossible de configurer lapplication '{app}' par défaut pour le domaine '{domain}' car il est déjà utilisé par l'application '{other_app}'",
"app_make_default_location_already_used": "Impossible de configurer lapplication '{app}' par défaut pour le domaine '{domain}' car il est déjà utilisé par lapplication '{other_app}'",
"app_upgrade_app_name": "Mise à jour de lapplication {app} …",
"backup_output_symlink_dir_broken": "Votre répertoire d'archivage '{path:s}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.",
"backup_output_symlink_dir_broken": "Votre répertoire darchivage '{path:s}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.",
"migrate_tsig_end": "La migration à HMAC-SHA-512 est terminée",
"migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}",
"migrate_tsig_start": "Lalgorithme de génération des clefs nest pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers HMAC-SHA-512 qui est plus sécurisé",
@ -270,13 +270,13 @@
"migration_0003_patching_sources_list": "Modification du fichier sources.lists …",
"migration_0003_main_upgrade": "Démarrage de la mise à niveau principale …",
"migration_0003_fail2ban_upgrade": "Démarrage de la mise à niveau de fail2ban …",
"migration_0003_restoring_origin_nginx_conf": "Votre fichier /etc/nginx/nginx.conf a été modifié dune manière ou dune autre. La migration va dabords le réinitialiser à son état initial. Le fichier précédent sera disponible en tant que {backup_dest}.",
"migration_0003_restoring_origin_nginx_conf": "Votre fichier /etc/nginx/nginx.conf a été modifié dune manière ou dune autre. La migration va dabord le réinitialiser à son état initial. Le fichier précédent sera disponible en tant que {backup_dest}.",
"migration_0003_yunohost_upgrade": "Démarrage de la mise à niveau du paquet YunoHost. La migration se terminera, mais la mise à jour réelle aura lieu immédiatement après. Une fois cette opération terminée, vous pourriez avoir à vous reconnecter à ladministration via le panel web.",
"migration_0003_not_jessie": "La distribution Debian actuelle nest pas Jessie !",
"migration_0003_system_not_fully_up_to_date": "Votre système nest pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.",
"migration_0003_system_not_fully_up_to_date": "Votre système nest pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer la migration à Stretch.",
"migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose sest mal passé pendant la mise à niveau principale : le système est toujours sur Debian Jessie !? Pour investiguer sur le problème, veuillez regarder les journaux {log}:s …",
"migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si léquipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus dinformations sur https://yunohost.org/backup ;\n - dêtre patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusquà quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). Lancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence.",
"migration_0003_problematic_apps_warning": "Veuillez noter que les applications installées potentiellement problématiques suivantes ont été détectées. Il semble que celles-ci n'ont pas été installées à partir d'un catalogue d'applications, ou ne sont pas marquées comme \"fonctionnelle\". Par conséquent, il ne peut pas être garanti qu'ils fonctionneront toujours après la mise à niveau: {problematic_apps}",
"migration_0003_problematic_apps_warning": "Veuillez noter que les applications installées potentiellement problématiques suivantes ont été détectées. Il semble que celles-ci nont pas été installées à partir dun catalogue dapplications, ou ne sont pas marquées comme \"fonctionnelle\". Par conséquent, il ne peut pas être garanti quils fonctionneront toujours après la mise à niveau: {problematic_apps}",
"migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}",
"migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.",
"migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans linterface admin, ou lancer `yunohost tools migrations migrate`.",
@ -295,15 +295,15 @@
"service_description_slapd": "Stocke les utilisateurs, domaines et leurs informations liées",
"service_description_ssh": "Vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)",
"service_description_yunohost-api": "Permet les interactions entre linterface web de YunoHost et le système",
"service_description_yunohost-firewall": "Gère l'ouverture et la fermeture des ports de connexion aux services",
"service_description_yunohost-firewall": "Gère louverture et la fermeture des ports de connexion aux services",
"experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas lutiliser à moins que vous ne sachiez ce que vous faites.",
"log_corrupted_md_file": "Le fichier YAML de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}",
"log_category_404": "Le journal de la catégorie '{category}' nexiste pas",
"log_link_to_log": "Journal complet de cette opération : '<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\"> {desc} </a>'",
"log_help_to_get_log": "Pour voir le journal de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'",
"log_link_to_failed_log": "Lopération '{desc}' a échouée ! Pour obtenir de laide, merci de partager le journal de l'opération en <a href=\"#/tools/logs/{name}\">cliquant ici</a>",
"log_link_to_failed_log": "Lopération '{desc}' a échoué ! Pour obtenir de laide, merci de partager le journal de lopération en <a href=\"#/tools/logs/{name}\">cliquant ici</a>",
"backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge PHP 7, vous pourriez ne plus pouvoir restaurer vos applications PHP (cause : {error:s})",
"log_help_to_get_failed_log": "Lopération '{desc}' a échouée ! Pour obtenir de laide, merci de partager le journal de l'opération en utilisant la commande 'yunohost log display {name} --share'",
"log_help_to_get_failed_log": "Lopération '{desc}' a échoué ! Pour obtenir de laide, merci de partager le journal de lopération en utilisant la commande 'yunohost log display {name} --share'",
"log_does_exists": "Il nexiste pas de journal de lopération ayant pour nom '{log}', utiliser 'yunohost log list' pour voir tous les fichiers de journaux disponibles",
"log_operation_unit_unclosed_properly": "Lopération ne sest pas terminée correctement",
"log_app_change_url": "Changer lURL de lapplication '{}'",
@ -337,14 +337,14 @@
"migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6",
"migration_0005_postgresql_94_not_installed": "PostgreSQL na pas été installé sur votre système. Rien à faire !",
"migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose détrange a dû arriver à votre système… :(",
"migration_0005_not_enough_space": "Laissez suffisamment d'espace disponible dans {path} pour exécuter la migration.",
"migration_0005_not_enough_space": "Laissez suffisamment despace disponible dans {path} pour exécuter la migration.",
"service_description_php7.0-fpm": "Exécute des applications écrites en PHP avec NGINX",
"users_available": "Liste des utilisateurs disponibles :",
"good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe dadministration. Le mot de passe doit comporter au moins 8 caractères bien quil soit recommandé dutiliser un mot de passe plus long (cest-à-dire une phrase secrète) et/ou dutiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).",
"good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères - bien quil soit recommandé dutiliser un mot de passe plus long (cest-à-dire une phrase secrète) et/ou dutiliser différents types de caractères tels que : majuscules, minuscules, chiffres et caractères spéciaux.",
"migration_description_0006_sync_admin_and_root_passwords": "Synchroniser les mots de passe admin et root",
"migration_0006_disclaimer": "YunoHost s'attend maintenant à ce que les mots de passe administrateur et racine soient synchronisés. Cette migration remplace votre mot de passe root par le mot de passe administrateur.",
"password_listed": "Ce mot de passe est l'un des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose d'un peu plus singulier.",
"migration_0006_disclaimer": "YunoHost sattend maintenant à ce que les mots de passe administrateur et racine soient synchronisés. Cette migration remplace votre mot de passe root par le mot de passe administrateur.",
"password_listed": "Ce mot de passe est lun des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose dun peu plus singulier.",
"password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères",
"password_too_simple_2": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules et des minuscules",
"password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux",
@ -352,33 +352,33 @@
"root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost na pas pu le propager au mot de passe root !",
"aborting": "Annulation.",
"app_not_upgraded": "Lapplication {failed_app} na pas été mise à jour et par conséquence les applications suivantes nont pas été mises à jour : {apps}",
"app_start_install": "Installation de l'application {app} …",
"app_start_remove": "Suppression de l'application {app} …",
"app_start_backup": "Collecte des fichiers devant être sauvegardés pour l'application {app} …",
"app_start_restore": "Restauration de l'application {app} …",
"app_start_install": "Installation de lapplication {app} …",
"app_start_remove": "Suppression de lapplication {app} …",
"app_start_backup": "Collecte des fichiers devant être sauvegardés pour lapplication {app} …",
"app_start_restore": "Restauration de lapplication {app} …",
"app_upgrade_several_apps": "Les applications suivantes seront mises à jour : {apps}",
"ask_new_domain": "Nouveau domaine",
"ask_new_path": "Nouveau chemin",
"backup_actually_backuping": "Création d'une archive de sauvegarde à partir des fichiers collectés …",
"backup_mount_archive_for_restore": "Préparation de l'archive pour restauration …",
"confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n'est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l'authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L'installer quand même ? [{answers:s}] ",
"confirm_app_install_danger": "DANGER! Cette application est connue pour être encore expérimentale (si elle ne fonctionne pas explicitement)! Vous ne devriez probablement PAS l'installer à moins de savoir ce que vous faites. AUCUN SUPPORT ne sera fourni si cette application ne fonctionne pas ou casse votre système ... Si vous êtes prêt à prendre ce risque de toute façon, tapez '{answers:s}'",
"confirm_app_install_thirdparty": "DANGER! Cette application ne fait pas partie du catalogue d'applications de Yunohost. L'installation d'applications tierces peut compromettre l'intégrité et la sécurité de votre système. Vous ne devriez probablement PAS l'installer à moins de savoir ce que vous faites. AUCUN SUPPORT ne sera fourni si cette application ne fonctionne pas ou casse votre système ... Si vous êtes prêt à prendre ce risque de toute façon, tapez '{answers:s}'",
"backup_actually_backuping": "Création dune archive de sauvegarde à partir des fichiers collectés …",
"backup_mount_archive_for_restore": "Préparation de larchive pour restauration …",
"confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais nest pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que lauthentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. Linstaller quand même ? [{answers:s}] ",
"confirm_app_install_danger": "DANGER ! Cette application est connue pour être encore expérimentale (si elle ne fonctionne pas explicitement) ! Vous ne devriez probablement PAS linstaller à moins de savoir ce que vous faites. AUCUN SUPPORT ne sera fourni si cette application ne fonctionne pas ou casse votre système Si vous êtes prêt à prendre ce risque de toute façon, tapez '{answers:s}'",
"confirm_app_install_thirdparty": "DANGER ! Cette application ne fait pas partie du catalogue dapplications de YunoHost. Linstallation dapplications tierces peut compromettre lintégrité et la sécurité de votre système. Vous ne devriez probablement PAS linstaller à moins de savoir ce que vous faites. AUCUN SUPPORT ne sera fourni si cette application ne fonctionne pas ou casse votre système Si vous êtes prêt à prendre ce risque de toute façon, tapez '{answers:s}'",
"dpkg_is_broken": "Vous ne pouvez pas faire ça maintenant car dpkg/apt (le gestionnaire de paquets du système) semble avoir laissé des choses non configurées. Vous pouvez essayer de résoudre ce problème en vous connectant via SSH et en exécutant `sudo dpkg --configure -a'.",
"dyndns_could_not_check_available": "Impossible de vérifier si {domain:s} est disponible chez {provider:s}.",
"file_does_not_exist": "Le fichier dont le chemin est {path:s} n'existe pas.",
"file_does_not_exist": "Le fichier dont le chemin est {path:s} nexiste pas.",
"global_settings_setting_security_password_admin_strength": "Qualité du mot de passe administrateur",
"global_settings_setting_security_password_user_strength": "Qualité du mot de passe de l'utilisateur",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Autoriser l'utilisation de la clé hôte DSA (obsolète) pour la configuration du service SSH",
"global_settings_setting_security_password_user_strength": "Qualité du mot de passe de lutilisateur",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Autoriser lutilisation de la clé hôte DSA (obsolète) pour la configuration du service SSH",
"hook_json_return_error": "Échec de la lecture au retour du script {path:s}. Erreur : {msg:s}. Contenu brut : {raw_content}",
"migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuration SSH sera gérée par YunoHost (étape 1, automatique)",
"migration_description_0008_ssh_conf_managed_by_yunohost_step2": "La configuration SSH sera gérée par YunoHost (étape 2, manuelle)",
"migration_0007_cancelled": "Impossible d'améliorer la gestion de votre configuration SSH.",
"migration_0007_cannot_restart": "SSH ne peut pas être redémarré après avoir essayé d'annuler la migration numéro 6.",
"migration_0007_cancelled": "Impossible daméliorer la gestion de votre configuration SSH.",
"migration_0007_cannot_restart": "SSH ne peut pas être redémarré après avoir essayé dannuler la migration numéro 6.",
"migration_0008_general_disclaimer": "Pour améliorer la sécurité de votre serveur, il est recommandé de laisser YunoHost gérer la configuration SSH. Votre configuration SSH actuelle diffère de la configuration recommandée. Si vous laissez YunoHost la reconfigurer, la façon dont vous vous connectez à votre serveur via SSH changera comme suit :",
"migration_0008_port": "- Vous devrez vous connecter en utilisant le port 22 au lieu de votre actuel port SSH personnalisé. N'hésitez pas à le reconfigurer ;",
"migration_0008_root": "- Vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l'utilisateur admin ;",
"migration_0008_dsa": "- La clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d'invalider un avertissement effrayant de votre client SSH afin de revérifier l'empreinte de votre serveur ;",
"migration_0008_port": "- Vous devrez vous connecter en utilisant le port 22 au lieu de votre actuel port SSH personnalisé. Nhésitez pas à le reconfigurer ;",
"migration_0008_root": "- Vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser lutilisateur admin ;",
"migration_0008_dsa": "- La clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin dinvalider un avertissement effrayant de votre client SSH afin de revérifier lempreinte de votre serveur ;",
"migration_0008_warning": "Si vous comprenez ces avertissements et souhaitez que YunoHost écrase votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.",
"migration_0008_no_warning": "Remplacer votre configuration SSH devrait être sûr, bien que cela ne puisse être promis ! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.",
"migrations_success": "Migration {number} {name} réussie !",
@ -391,10 +391,10 @@
"service_reload_or_restart_failed": "Impossible de recharger ou de redémarrer le service '{service:s}'\n\nJournaux historisés récents de ce service : {logs:s}",
"service_reloaded_or_restarted": "Le service « {service:s} » a été rechargé ou redémarré",
"this_action_broke_dpkg": "Cette action a laissé des paquets non configurés par dpkg/apt (les gestionnaires de paquets système). Vous pouvez essayer de résoudre ce problème en vous connectant via SSH et en exécutant `sudo dpkg --configure -a`.",
"app_action_cannot_be_ran_because_required_services_down": "Ces services requis doivent être en cours d'exécution pour exécuter cette action: {services}. Essayez de les redémarrer pour continuer (et éventuellement rechercher pourquoi ils sont en panne).",
"app_action_cannot_be_ran_because_required_services_down": "Ces services requis doivent être en cours dexécution pour exécuter cette action: {services}. Essayez de les redémarrer pour continuer (et éventuellement rechercher pourquoi ils sont en panne).",
"admin_password_too_long": "Veuillez choisir un mot de passe de moins de 127 caractères",
"log_regen_conf": "Régénérer les configurations du système '{}'",
"migration_0009_not_needed": "Cette migration semble avoir déjà été jouée ? On l'ignore.",
"migration_0009_not_needed": "Cette migration semble avoir déjà été jouée ? On lignore.",
"regenconf_file_backed_up": "Le fichier de configuration '{conf}' a été sauvegardé sous '{backup}'",
"regenconf_file_copy_failed": "Impossible de copier le nouveau fichier de configuration '{new}' vers '{conf}'",
"regenconf_file_manually_modified": "Le fichier de configuration '{conf}' a été modifié manuellement et ne sera pas mis à jour",
@ -404,12 +404,12 @@
"regenconf_file_updated": "Le fichier de configuration '{conf}' a été mis à jour",
"regenconf_now_managed_by_yunohost": "Le fichier de configuration '{conf}' est maintenant géré par YunoHost (catégorie {category}).",
"regenconf_up_to_date": "La configuration est déjà à jour pour la catégorie '{category}'",
"already_up_to_date": "Il n'y a rien à faire ! Tout est déjà à jour !",
"global_settings_setting_security_nginx_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur web nginx. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)",
"global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)",
"global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)",
"already_up_to_date": "Il ny a rien à faire ! Tout est déjà à jour !",
"global_settings_setting_security_nginx_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur web nginx. Affecte les cryptogrammes (et dautres aspects liés à la sécurité)",
"global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et dautres aspects liés à la sécurité)",
"global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et dautres aspects liés à la sécurité)",
"migration_description_0009_decouple_regenconf_from_services": "Dissocier le mécanisme « regen-conf » des services",
"migration_description_0010_migrate_to_apps_json": "Supprimer les catalogues d'applications obsolètes afin d'utiliser la nouvelle liste unifiée 'apps.json' à la place (les anciens catalogues seront remplacés durant la migration 13)",
"migration_description_0010_migrate_to_apps_json": "Supprimer les catalogues dapplications obsolètes afin dutiliser la nouvelle liste unifiée 'apps.json' à la place (les anciens catalogues seront remplacés durant la migration 13)",
"regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par « regen-conf » (catégorie {category}) mais a été conservé.",
"regenconf_updated": "La configuration a été mise à jour pour '{category}'",
"regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'",
@ -429,7 +429,7 @@
"tools_upgrade_special_packages_explanation": "La mise à jour spéciale va continuer en arrière-plan. Veuillez ne pas lancer dautres actions sur votre serveur pendant environ 10 minutes (en fonction de la vitesse du matériel). Après cela, il vous faudra peut-être vous reconnecter à la webadmin. Le journal de mise à niveau sera disponible dans Outils → Journal (dans la webadmin) ou via \"yunohost log list\" (en ligne de commande).",
"update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}",
"update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}",
"backup_permission": "Permission de sauvegarde pour l'application {app:s}",
"backup_permission": "Permission de sauvegarde pour lapplication {app:s}",
"group_created": "Le groupe '{group}' a été créé",
"group_deleted": "Suppression du groupe '{group}'",
"group_unknown": "Le groupe {group:s} est inconnu",
@ -439,23 +439,23 @@
"group_deletion_failed": "Échec de la suppression du groupe '{group}': {error}",
"log_user_group_delete": "Supprimer le groupe '{}'",
"log_user_group_update": "Mettre à jour '{}' pour le groupe",
"mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}",
"mailbox_disabled": "La boîte aux lettres est désactivée pour lutilisateur {user:s}",
"app_action_broke_system": "Cette action semble avoir cassé des services importants : {services}",
"apps_already_up_to_date": "Toutes les applications sont déjà à jour",
"migration_0011_create_group": "Création d'un groupe pour chaque utilisateur…",
"migration_0011_done": "Migration terminée. Vous êtes maintenant en mesure de gérer des groupes d'utilisateurs.",
"migration_0011_create_group": "Création dun groupe pour chaque utilisateur…",
"migration_0011_done": "Migration terminée. Vous êtes maintenant en mesure de gérer des groupes dutilisateurs.",
"migrations_must_provide_explicit_targets": "Vous devez fournir des cibles explicites lorsque vous utilisez '--skip' ou '--force-rerun'",
"migrations_no_such_migration": "Il n'y a pas de migration appelée '{id}'",
"migrations_no_such_migration": "Il ny a pas de migration appelée '{id}'",
"migrations_pending_cant_rerun": "Ces migrations étant toujours en attente, vous ne pouvez pas les exécuter à nouveau : {ids}",
"migration_description_0012_postgresql_password_to_md5_authentication": "Forcer l'authentification PostgreSQL à utiliser MD5 pour les connexions locales",
"migration_description_0012_postgresql_password_to_md5_authentication": "Forcer lauthentification PostgreSQL à utiliser MD5 pour les connexions locales",
"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}",
"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 na 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_migration_failed_trying_to_rollback": "La migration a échouée… 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_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 dune 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_update_failed": "Impossible de mettre à jour la permission '{permission}' : {error}",
"permission_updated": "Permission '{permission:s}' mise à jour",
@ -467,27 +467,27 @@
"migrations_failed_to_load_migration": "Impossible de charger la migration {id}: {error}",
"migrations_running_forward": "Exécution de la migration {id} …",
"migrations_success_forward": "Migration {id} terminée",
"operation_interrupted": "L'opération a été interrompue manuellement ?",
"permission_already_exist": "L'autorisation '{permission}' existe déjà",
"operation_interrupted": "Lopération a été interrompue manuellement ?",
"permission_already_exist": "Lautorisation '{permission}' existe déjà",
"permission_created": "Permission '{permission:s}' créée",
"permission_creation_failed": "Impossible de créer l'autorisation '{permission}': {error}",
"permission_creation_failed": "Impossible de créer lautorisation '{permission}' : {error}",
"permission_deleted": "Permission '{permission:s}' supprimée",
"permission_deletion_failed": "Impossible de supprimer la permission '{permission}' : {error}",
"migration_description_0011_setup_group_permission": "Initialiser les groupes d'utilisateurs et autorisations pour les applications et les services",
"migration_description_0011_setup_group_permission": "Initialiser les groupes dutilisateurs et autorisations pour les applications et les services",
"migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error:s}",
"group_already_exist": "Le groupe {group} existe déjà",
"group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système",
"group_cannot_be_deleted": "Le groupe {group} ne peut pas être supprimé manuellement.",
"group_user_already_in_group": "L'utilisateur {user} est déjà dans le groupe {group}",
"group_user_not_in_group": "L'utilisateur {user} n'est pas dans le groupe {group}",
"group_user_already_in_group": "Lutilisateur {user} est déjà dans le groupe {group}",
"group_user_not_in_group": "Lutilisateur {user} nest pas dans le groupe {group}",
"log_permission_create": "Créer permission '{}'",
"log_permission_delete": "supprimer permission '{}'",
"log_permission_delete": "Supprimer permission '{}'",
"log_user_group_create": "Créer '{}' groupe",
"log_user_permission_update": "Mise à jour des accès pour la permission '{}'",
"log_user_permission_reset": "Réinitialiser la permission '{}'",
"migration_0011_failed_to_remove_stale_object": "Impossible de supprimer un objet périmé {dn}: {error}",
"permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée",
"permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé '",
"permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé",
"permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé",
"user_already_exists": "L'utilisateur '{user}' existe déjà",
"app_full_domain_unavailable": "Désolé, cette application doit être installée sur un domaine qui lui est propre, mais d'autres applications sont déjà installées sur le domaine '{domain}'. Vous pouvez utiliser un sous-domaine dédié à cette application à la place.",
@ -496,64 +496,73 @@
"group_cannot_edit_primary_group": "Le groupe '{group}' ne peut pas être édité manuellement. C'est le groupe principal destiné à ne contenir qu'un utilisateur spécifique.",
"log_permission_url": "Mise à jour de l'URL associée à l'autorisation '{}'",
"migration_0011_slapd_config_will_be_overwritten": "Il semble que vous ayez modifié manuellement la configuration de slapd. Pour cette migration critique, YunoHost doit forcer la mise à jour de la configuration de slapd. Les fichiers originaux seront sauvegardés dans {conf_backup_folder}.",
"permission_already_up_to_date": "L'autorisation n'a pas été mise à jour car les demandes d'ajout/suppression correspondent déjà à l'état actuel.",
"permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l'autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.",
"app_install_failed": "Impossible d'installer {app}: {error}",
"app_install_script_failed": "Une erreur est survenue dans le script d'installation de l'application",
"permission_require_account": "Permission {permission} n'a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs.",
"app_remove_after_failed_install": "Supprimer l'application après l'échec de l'installation…",
"diagnosis_display_tip_web": "Vous pouvez aller à la section Diagnostic (dans l'écran d'accueil) pour voir les problèmes rencontrés.",
"diagnosis_cant_run_because_of_dep": "Impossible d'exécuter le diagnostic pour {category} alors qu'il existe des problèmes importants liés à {dep}.",
"permission_already_up_to_date": "Lautorisation na pas été mise à jour car les demandes dajout/suppression correspondent déjà à létat actuel.",
"permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer lautorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.",
"app_install_failed": "Impossible dinstaller {app}: {error}",
"app_install_script_failed": "Une erreur est survenue dans le script dinstallation de lapplication",
"permission_require_account": "Permission {permission} na de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs.",
"app_remove_after_failed_install": "Supprimer lapplication après léchec de linstallation …",
"diagnosis_display_tip_web": "Vous pouvez aller à la section Diagnostic (dans lécran daccueil) pour voir les problèmes rencontrés.",
"diagnosis_cant_run_because_of_dep": "Impossible dexécuter le diagnostic pour {category} alors quil existe des problèmes importants liés à {dep}.",
"diagnosis_found_errors": "Trouvé {errors} problème(s) significatif(s) lié(s) à {category} !",
"diagnosis_found_errors_and_warnings": "Trouvé {errors} problème(s) significatif(s) (et {warnings} (avertissement(s)) en relation avec {category} !",
"diagnosis_ip_not_connected_at_all": "Le serveur ne semble pas du tout connecté à Internet !?",
"diagnosis_ip_weird_resolvconf": "La résolution DNS semble fonctionner, mais soyez prudent en utilisant un fichier /etc/resolv.conf personnalisé.",
"diagnosis_ip_weird_resolvconf_details": "Au lieu de cela, ce fichier devrait être un lien symbolique vers /etc/resolvconf/run/resolv.conf lui-même pointant vers 127.0.0.1 (dnsmasq). Les résolveurs réels doivent être configurés dans /etc/resolv.dnsmasq.conf.",
"diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS de type {0}, nom {1} et valeur {2}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.",
"diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a encore {free_abs_GB} Go ({free_percent}%) d'espace libre !",
"diagnosis_ram_ok": "Le système dispose encore de {available_abs_MB} MB ({available_percent}%) de RAM sur {total_abs_MB} MB.",
"diagnosis_dns_missing_record": "Selon la configuration DNS recommandée, vous devez ajouter un enregistrement DNS\nType: {type}\nNom: {name}\nValeur {value}",
"diagnosis_diskusage_ok": "Le stockage {mountpoint} (sur le périphérique {device}) a encore {free} ({free_percent}%) despace libre !",
"diagnosis_ram_ok": "Le système dispose encore de {available} ({available_percent}%) de RAM sur {total}.",
"diagnosis_regenconf_allgood": "Tous les fichiers de configuration sont conformes à la configuration recommandée !",
"diagnosis_security_vulnerable_to_meltdown": "Vous semblez vulnérable à la vulnérabilité de sécurité critique de Meltdown",
"diagnosis_basesystem_host": "Le serveur utilise Debian {debian_version}",
"diagnosis_basesystem_kernel": "Le serveur utilise le noyau Linux {kernel_version}",
"diagnosis_basesystem_ynh_single_version": "{0} version: {1} ({2})",
"diagnosis_basesystem_ynh_single_version": "{package} version: {version} ({repo})",
"diagnosis_basesystem_ynh_main_version": "Le serveur utilise YunoHost {main_version} ({repo})",
"diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost ... probablement à cause d'une mise à niveau partielle ou échouée.",
"diagnosis_basesystem_ynh_inconsistent_versions": "Vous exécutez des versions incohérentes des packages YunoHost … probablement à cause dune mise à niveau partielle ou échouée.",
"diagnosis_display_tip_cli": "Vous pouvez exécuter 'yunohost diagnosis show --issues' pour afficher les problèmes détectés.",
"diagnosis_failed_for_category": "Échec du diagnostic pour la catégorie '{category}': {error}",
"diagnosis_cache_still_valid": "(Le cache est toujours valide pour le diagnostic {category}. Pas re-diagnostiquer pour le moment!)",
"diagnosis_cache_still_valid": "(Le cache est encore valide pour le diagnostic {category}. Il ne sera pas re-diagnostiqué pour le moment!)",
"diagnosis_ignored_issues": "(+ {nb_ignored} questions ignorée(s))",
"diagnosis_found_warnings": "Trouvé {warnings} objet(s) pouvant être amélioré(s) pour {category}.",
"diagnosis_everything_ok": "Tout semble bien pour {category} !",
"diagnosis_failed": "Impossible d'extraire le résultat du diagnostic pour la catégorie '{category}': {error}",
"diagnosis_failed": "Impossible dextraire le résultat du diagnostic pour la catégorie '{category}': {error}",
"diagnosis_ip_connected_ipv4": "Le serveur est connecté à Internet en IPv4 !",
"diagnosis_ip_no_ipv4": "Le serveur ne dispose pas dune adresse IPv4.",
"diagnosis_ip_connected_ipv6": "Le serveur est connecté à Internet en IPv6 !",
"diagnosis_ip_no_ipv6": "Le serveur ne dispose pas d'une adresse IPv6.",
"diagnosis_ip_no_ipv6": "Le serveur ne dispose pas dune adresse IPv6.",
"diagnosis_ip_dnsresolution_working": "La résolution de nom de domaine fonctionne !",
"diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque... Un pare-feu bloque-t-il les requêtes DNS ?",
"diagnosis_ip_broken_dnsresolution": "La résolution du nom de domaine semble interrompue pour une raison quelconque Un pare-feu bloque-t-il les requêtes DNS ?",
"diagnosis_ip_broken_resolvconf": "La résolution du nom de domaine semble cassée sur votre serveur, ce qui semble lié au fait que /etc/resolv.conf ne pointe pas vers 127.0.0.1.",
"diagnosis_dns_good_conf": "Bonne configuration DNS pour le domaine {domain} (catégorie {category})",
"diagnosis_dns_bad_conf": "Configuration DNS incorrecte ou manquante pour le domaine {domain} (catégorie {category})",
"diagnosis_dns_discrepancy": "L'enregistrement DNS de type {0} et nom {1} ne correspond pas à la configuration recommandée. Valeur actuelle: {2}. Valeur exceptée: {3}. Vous pouvez consulter https://yunohost.org/dns_config pour plus d'informations.",
"diagnosis_dns_discrepancy": "Lenregistrement DNS de type {0} et nom {1} ne correspond pas à la configuration recommandée. Valeur actuelle: {2}. Valeur exceptée: {3}. Vous pouvez consulter https://yunohost.org/dns_config pour plus dinformations.",
"diagnosis_services_bad_status": "Le service {service} est {status} :-(",
"diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu d'espace.",
"diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu despace.",
"diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free_abs_GB} Go ({free_percent}%). Faites attention.",
"diagnosis_ram_verylow": "Le système ne dispose plus que de {available_abs_MB} MB ({available_percent}%) ! (sur {total_abs_MB} Mo)",
"diagnosis_ram_low": "Le système n'a plus de {available_abs_MB} MB ({available_percent}%) RAM sur {total_abs_MB} MB. Faites attention.",
"diagnosis_swap_none": "Le système n'a aucun échange. Vous devez envisager dajouter au moins 256 Mo de swap pour éviter les situations où le système manque de mémoire.",
"diagnosis_swap_notsomuch": "Le système ne dispose que de {total_MB} Mo de swap. Vous devez envisager d'avoir au moins 256 Mo pour éviter les situations où le système manque de mémoire.",
"diagnosis_ram_low": "Le système na plus de {available_abs_MB} MB ({available_percent}%) RAM sur {total_abs_MB} MB. Faites attention.",
"diagnosis_swap_none": "Le système na aucun échange. Vous devez envisager dajouter au moins 256 Mo de swap pour éviter les situations où le système manque de mémoire.",
"diagnosis_swap_notsomuch": "Le système ne dispose que de {total_MB} Mo de swap. Vous devez envisager davoir au moins 256 Mo pour éviter les situations où le système manque de mémoire.",
"diagnosis_swap_ok": "Le système dispose de {total_MB} Mo de swap !",
"diagnosis_dns_discrepancy": "Lenregistrement DNS de type {type} et nom {name} ne correspond pas à la configuration recommandée.\nValeur actuelle: {current}\nValeur attendue: {value}",
"diagnosis_services_bad_status": "Le service {service} est {status} :-(",
"diagnosis_diskusage_verylow": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free} ({free_percent}%). Vous devriez vraiment envisager de nettoyer un peu despace.",
"diagnosis_diskusage_low": "Le stockage {mountpoint} (sur le périphérique {device}) ne dispose que de {free} ({free_percent}%). Faites attention.",
"diagnosis_ram_verylow": "Le système ne dispose plus que de {available} ({available_percent}%)! (sur {total})",
"diagnosis_ram_low": "Le système na plus de {available} ({available_percent}%) RAM sur {total}. Faites attention.",
"diagnosis_swap_none": "Le système na aucun espace de swap. Vous devriez envisager dajouter au moins {recommended} de swap pour éviter les situations où le système manque de mémoire.",
"diagnosis_swap_notsomuch": "Le système ne dispose que de {total} de swap. Vous devez envisager davoir au moins {recommended} pour éviter les situations où le système manque de mémoire.",
"diagnosis_swap_ok": "Le système dispose de {total} de swap !",
"diagnosis_regenconf_manually_modified": "Le fichier de configuration {file} a été modifié manuellement.",
"diagnosis_regenconf_manually_modified_debian": "Le fichier de configuration {file} a été modifié manuellement par rapport à celui par défaut de Debian.",
"diagnosis_regenconf_manually_modified_details": "C'est probablement OK tant que vous savez ce que vous faites;) !",
"diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus ...",
"diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique n'a été trouvée.",
"apps_catalog_init_success": "Système de catalogue d'applications initialisé !",
"diagnosis_regenconf_manually_modified_details": "Cest probablement OK tant que vous savez ce que vous faites ;) !",
"diagnosis_regenconf_manually_modified_debian_details": "Cela peut probablement être OK, mais il faut garder un œil dessus ",
"diagnosis_security_all_good": "Aucune vulnérabilité de sécurité critique na été trouvée.",
"apps_catalog_init_success": "Système de catalogue dapplications initialisé !",
"apps_catalog_failed_to_download": "Impossible de télécharger le catalogue des applications {apps_catalog}:{error}",
"diagnosis_mail_ougoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à d'autres serveurs.",
"domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il s'agit du domaine principal et de votre seul domaine. Vous devez d'abord ajouter un autre domaine à l'aide de 'yunohost domain add <another-domain.com>', puis définir comme domaine principal à l'aide de ' yunohost domain main-domain -n <nomd'un-autre-domaine.com>' et vous pouvez ensuite supprimer le domaine '{domain:s}' à l'aide de 'yunohost domain remove {domain:s}'.'",
"diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus d'informations.",
"diagnosis_mail_outgoing_port_25_blocked": "Le port sortant 25 semble être bloqué. Vous devriez essayer de le débloquer dans le panneau de configuration de votre fournisseur de services Internet (ou hébergeur). En attendant, le serveur ne pourra pas envoyer de courrier électronique à dautres serveurs.",
"domain_cannot_remove_main_add_new_one": "Vous ne pouvez pas supprimer '{domain:s}' car il sagit du domaine principal et de votre seul domaine. Vous devez dabord ajouter un autre domaine à laide de 'yunohost domain add <another-domain.com>', puis définir comme domaine principal à laide de 'yunohost domain main-domain -n <nom-dun-autre-domaine.com>' et vous pouvez ensuite supprimer le domaine '{domain:s}' à laide de 'yunohost domain remove {domain:s}'.",
"diagnosis_security_vulnerable_to_meltdown_details": "Pour résoudre ce problème, vous devez mettre à niveau votre système et redémarrer pour charger le nouveau noyau Linux (ou contacter votre fournisseur de serveur si cela ne fonctionne pas). Voir https://meltdownattack.com/ pour plus dinformations.",
"diagnosis_description_basesystem": "Système de base",
"diagnosis_description_ip": "Connectivité Internet",
"diagnosis_description_dnsrecords": "Enregistrements DNS",
@ -562,41 +571,42 @@
"diagnosis_description_ports": "Exposition des ports",
"diagnosis_description_regenconf": "Configurations système",
"diagnosis_description_security": "Contrôles de sécurité",
"diagnosis_ports_could_not_diagnose": "Impossible de diagnostiquer si les ports sont accessibles de l'extérieur. Erreur: {error}",
"apps_catalog_updating": "Mise à jour du catalogue d'applications...",
"diagnosis_ports_could_not_diagnose": "Impossible de diagnostiquer si les ports sont accessibles de l'extérieur.",
"diagnosis_ports_could_not_diagnose_details": "Erreur: {error}",
"apps_catalog_updating": "Mise à jour du catalogue d'applications…",
"apps_catalog_obsolete_cache": "Le cache du catalogue d'applications est vide ou obsolète.",
"apps_catalog_update_success": "Le catalogue des applications a été mis à jour !",
"diagnosis_mail_ougoing_port_25_ok": "Le port sortant 25 n'est pas bloqué et le courrier électronique peut être envoyé à d'autres serveurs.",
"diagnosis_mail_ougoing_port_25_ok": "Le port sortant 25 nest pas bloqué et le courrier électronique peut être envoyé à dautres serveurs.",
"diagnosis_description_mail": "Email",
"diagnosis_ports_unreachable": "Le port {port} n'est pas accessible de l'extérieur.",
"diagnosis_ports_ok": "Le port {port} est accessible de l'extérieur.",
"diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de l'extérieur. Erreur: {error}",
"diagnosis_http_ok": "Le domaine {domain} est accessible au travers de HTTP depuis l'extérieur.",
"diagnosis_http_unreachable": "Le domaine {domain} est inaccessible au travers de HTTP depuis l'extérieur.",
"diagnosis_ports_unreachable": "Le port {port} nest pas accessible de lextérieur.",
"diagnosis_ports_ok": "Le port {port} est accessible de lextérieur.",
"diagnosis_http_could_not_diagnose": "Impossible de diagnostiquer si le domaine est accessible de lextérieur.",
"diagnosis_http_could_not_diagnose_details": "Erreur: {error}",
"diagnosis_http_ok": "Le domaine {domain} est accessible en HTTP depuis lextérieur.",
"diagnosis_http_unreachable": "Le domaine {domain} est inaccessible en HTTP depuis lextérieur.",
"diagnosis_unknown_categories": "Les catégories suivantes sont inconnues: {categories}",
"migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue d'applications à l'épreuve du temps",
"app_upgrade_script_failed": "Une erreur s'est produite durant lexécution du script de mise à niveau de l'application",
"migration_description_0014_remove_app_status_json": "Supprimer les fichiers d'application status.json hérités",
"diagnosis_services_running": "Le service {service} s'exécute correctement !",
"migration_description_0013_futureproof_apps_catalog_system": "Migrer vers le nouveau système de catalogue dapplications à lépreuve du temps",
"app_upgrade_script_failed": "Une erreur sest produite durant lexécution du script de mise à niveau de lapplication",
"migration_description_0014_remove_app_status_json": "Supprimer les anciens fichiers dapplication status.json",
"diagnosis_services_running": "Le service {service} est en cours de fonctionnement !",
"diagnosis_services_conf_broken": "La configuration est cassée pour le service {service} !",
"diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour les fonctionnalités de type {1} (service {0})",
"diagnosis_ports_needed_by": "Rendre ce port accessible est nécessaire pour les fonctionnalités de type {category} (service {service})",
"diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit sur https://yunohost.org/isp_box_config",
"diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.",
"diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie « {category} »",
"diagnosis_http_unknown_error": "Une erreur est survenue en essayant de joindre votre domaine, il est probablement injoignable.",
"yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommendé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l'interface web (ou \"yunohost user create <nom d'utilisateur>\" en ligne de commande);\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande);\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans le guide de l'administrateur: https://yunohost.org/admindoc.",
"diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à l'aide de 'yunohost service log {0}' ou de la section 'Services' de l'administrateur Web.",
"diagnosis_http_bad_status_code": "Le système de diagnostique n'a pas réussi à contacter votre serveur. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration nginx est à jour et quun reverse-proxy ninterfère pas.",
"diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l'extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que nginx est en cours dexécution et quun pare-feu ninterfère pas.",
"yunohost_postinstall_end_tip": "La post-installation terminée! Pour finaliser votre configuration, il est recommendé de :\n - ajouter un premier utilisateur depuis la section \"Utilisateurs\" de linterface web (ou \"yunohost user create <nom dutilisateur>\" en ligne de commande);\n - diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou \"yunohost diagnosis run\" en ligne de commande);\n - lire les parties \"Finalisation de votre configuration\" et \"Découverte de Yunohost\" dans le guide de ladministrateur: https://yunohost.org/admindoc.",
"diagnosis_services_bad_status_tip": "Vous pouvez essayer de redémarrer le service. Si cela ne fonctionne pas, consultez les journaux de service à laide de 'yunohost service log {service}' ou de la section 'Services' dans la webadmin.",
"diagnosis_http_bad_status_code": "Le système de diagnostique na pas réussi à contacter votre serveur. Il se peut quune autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration nginx est à jour et quun reverse-proxy ninterfère pas.",
"diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de lextérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que nginx est en cours dexécution et quun pare-feu ninterfère pas.",
"global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie",
"log_app_action_run": "Lancer laction de lapplication '{}'",
"log_app_config_show_panel": "Montrer le panneau de configuration de lapplication '{}'",
"log_app_config_apply": "Appliquer la configuration à lapplication '{}'",
"diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu'il n'y a pas encore eu de diagnostic. Vous devriez en lancer un depuis le webmin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.",
"diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et quil ny a pas encore eu de diagnostic. Vous devriez en lancer un depuis le webmin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.",
"diagnosis_description_web": "Web",
"diagnosis_basesystem_hardware_board": "Le modèle de carte du serveur est {model}",
"diagnosis_basesystem_hardware": "L'architecture du serveur est {virt} {arch}",
"group_already_exist_on_system_but_removing_it": "Le groupe {group} est déjà présent dans les groupes du système, mais Yuhonost va le supprimer…",
"diagnosis_basesystem_hardware": "Larchitecture du serveur est {virt} {arch}",
"group_already_exist_on_system_but_removing_it": "Le groupe {group} est déjà présent dans les groupes du système, mais YunoHost va le supprimer…",
"certmanager_warning_subdomain_dns_record": "Le sous-domaine '{subdomain:s}' ne résout pas vers la même adresse IP que '{domain:s}'. Certaines fonctionnalités seront indisponibles tant que vous naurez pas corrigé cela et regénéré le certificat.",
"domain_cannot_add_xmpp_upload": "Vous ne pouvez pas ajouter de domaine commençant par 'xmpp-upload.'. Ce type de nom est réservé à la fonctionnalité dupload XMPP intégrée dans Yunohost."
"domain_cannot_add_xmpp_upload": "Vous ne pouvez pas ajouter de domaine commençant par 'xmpp-upload.'. Ce type de nom est réservé à la fonctionnalité dupload XMPP intégrée dans YunoHost."
}

View file

@ -479,8 +479,8 @@
"diagnosis_http_ok": "Lo domeni {domain} accessible de lexterior.",
"app_full_domain_unavailable": "Aquesta aplicacion a dèsser installada sul seu pròpri domeni, mas i a dautras aplicacions installadas sus aqueste domeni « {domain} ». Podètz utilizar allòc un josdomeni dedicat a aquesta aplicacion.",
"diagnosis_dns_bad_conf": "Configuracion DNS incorrècta o inexistenta pel domeni {domain} (categoria {category})",
"diagnosis_ram_verylow": "Lo sistèma a solament {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla ! (dun total de {total_abs_MB} MB)",
"diagnosis_ram_ok": "Lo sistèma a encara {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla dun total de {total_abs_MB} MB).",
"diagnosis_ram_verylow": "Lo sistèma a solament {available} ({available_percent}%) de memòria RAM disponibla ! (dun total de {total})",
"diagnosis_ram_ok": "Lo sistèma a encara {available} ({available_percent}%) de memòria RAM disponibla dun total de {total}).",
"permission_already_allowed": "Lo grop « {group} » a ja la permission « {permission} » activada",
"permission_already_disallowed": "Lo grop « {group} » a ja la permission « {permission} » desactivada",
"permission_cannot_remove_main": "La supression duna permission màger es pas autorizada",
@ -497,7 +497,7 @@
"user_already_exists": "Lutilizaire {user} existís ja",
"diagnosis_basesystem_host": "Lo servidor fonciona amb Debian {debian_version}.",
"diagnosis_basesystem_kernel": "Lo servidor fonciona amb lo nuclèu Linuxl {kernel_version}",
"diagnosis_basesystem_ynh_single_version": "{0} version : {1} ({2})",
"diagnosis_basesystem_ynh_single_version": "{package} version : {version} ({repo})",
"diagnosis_basesystem_ynh_inconsistent_versions": "Utilizatz de versions inconsistentas dels paquets de YunoHost… probablament a causa d'una actualizacion fracassada o parciala.",
"diagnosis_display_tip_cli": "Podètz executar « yunohost diagnosis show --issues » per mostrar las errors trobadas.",
"diagnosis_ignored_issues": "(+ {nb_ignored} problèma(es) ignorat(s))",
@ -511,7 +511,7 @@
"diagnosis_cache_still_valid": "(Memòria cache totjorn valida pel diagnostic {category}. Cap dautre diagnostic pel moment !)",
"diagnosis_found_errors": "{errors} errors importantas trobadas ligadas a {category} !",
"diagnosis_services_bad_status": "Lo servici {service} es {status} :(",
"diagnosis_swap_ok": "Lo sistèma a {total_MB} MB descambi !",
"diagnosis_swap_ok": "Lo sistèma a {total} descambi !",
"diagnosis_regenconf_allgood": "Totes los fichièrs de configuracion son confòrmes a la configuracion recomandada !",
"diagnosis_regenconf_manually_modified": "Lo fichièr de configuracion {file} foguèt modificat manualament.",
"diagnosis_regenconf_manually_modified_details": "Es probablament bon tan que sabètz çò que fasètz ;) !",
@ -527,7 +527,7 @@
"diagnosis_ports_ok": "Lo pòrt {port} es accessible de lexterior.",
"diagnosis_http_unreachable": "Lo domeni {domain} es pas accessible via HTTP de lexterior.",
"diagnosis_unknown_categories": "La categorias seguentas son desconegudas : {categories}",
"diagnosis_ram_low": "Lo sistèma a {available_abs_MB} Mo ({available_percent}%) de memòria RAM disponibla dun total de {total_abs_MB} MB). Atencion.",
"diagnosis_ram_low": "Lo sistèma a {available} ({available_percent}%) de memòria RAM disponibla dun total de {total}). Atencion.",
"diagnosis_regenconf_manually_modified_debian": "Lo fichier de configuracion {file} foguèt modificat manualament respècte al fichièr per defaut de Debian.",
"log_permission_create": "Crear la permission « {} »",
"log_permission_delete": "Suprimir la permission « {} »",
@ -536,11 +536,13 @@
"operation_interrupted": "Loperacion es estada interrompuda manualament ?",
"group_cannot_be_deleted": "Lo grop « {group} » pòt pas èsser suprimit manualament.",
"diagnosis_found_warnings": "Trobat {warnings} element(s) que se poirián melhorar per {category}.",
"diagnosis_dns_missing_record": "Segon la configuracion DNS recomandada, vos calriá ajustar un enregistrament DNS de tipe {0}, nom {1} e valor {2}. Podètz consultar https://yunohost.org/dns_config per mai dinformacions.",
"diagnosis_dns_discrepancy": "Segon la configuracion DNS recomandada, la valor per lenregistrament DNS de tipe {0} e nom {1} deuriá èsser {2} allòc de {3}.",
"diagnosis_dns_missing_record": "Segon la configuracion DNS recomandada, vos calriá ajustar un enregistrament DNS\ntipe: {type}\nnom: {name}\nvalor: {value}",
"diagnosis_dns_discrepancy": "Segon la configuracion DNS recomandada, la valor per lenregistrament DNS\ntipe: {type}\nnom: {name}\ndeuriá èsser: {current}\nallòc de: {value}",
"diagnosis_regenconf_manually_modified_debian_details": "Es pas problematic, mas car téner dagacher...",
"diagnosis_ports_could_not_diagnose": "Impossible de diagnosticar se los pòrts son accessibles de lexterior. Error : {error}",
"diagnosis_http_could_not_diagnose": "Impossible de diagnosticar se lo domeni es accessible de lexterior. Error : {error}",
"diagnosis_ports_could_not_diagnose": "Impossible de diagnosticar se los pòrts son accessibles de lexterior.",
"diagnosis_ports_could_not_diagnose_details": "Error : {error}",
"diagnosis_http_could_not_diagnose": "Impossible de diagnosticar se lo domeni es accessible de lexterior.",
"diagnosis_http_could_not_diagnose_details": "Error : {error}",
"apps_catalog_updating": "Actualizacion del catalòg daplicacion…",
"apps_catalog_failed_to_download": "Telecargament impossible del catalòg daplicacions {apps_catalog} : {error}",
"apps_catalog_obsolete_cache": "La memòria cache del catalòg daplicacion es voida o obsolèta.",
@ -556,19 +558,18 @@
"apps_catalog_init_success": "Sistèma de catalòg daplicacion iniciat!",
"diagnosis_services_running": "Lo servici {service} es lançat!",
"diagnosis_services_conf_broken": "La configuracion es copada pel servici {service}!",
"diagnosis_ports_needed_by": "Es necessari quaqueste pòrt siá accessible pel servici {0}",
"diagnosis_diskusage_low": "Lo lòc demmagazinatge {mountpoint} (sul periferic {device}) a solament {free_abs_GB} Go ({free_percent}%). Siatz prudent.",
"diagnosis_ports_needed_by": "Es necessari quaqueste pòrt siá accessible pel servici {service}",
"diagnosis_diskusage_low": "Lo lòc demmagazinatge {mountpoint} (sul periferic {device}) a solament {free} ({free_percent}%). Siatz prudent.",
"migration_description_0014_remove_app_status_json": "Suprimir los fichièrs daplicacion status.json eretats",
"dyndns_provider_unreachable": "Impossible datenher lo provesidor Dyndns: siá vòstre YunoHost es pas corrèctament connectat a Internet siá lo servidor dynette es copat.",
"diagnosis_services_bad_status_tip": "Podètz ensajar de reaviar lo servici, e se non fonciona pas, podètz agachar los jornals en utilizant «yunohost service log {0} » o via la seccion «Servicis» de pas la pagina web dadministracion.",
"diagnosis_services_bad_status_tip": "Podètz ensajar de reaviar lo servici, e se non fonciona pas, podètz agachar los jornals en utilizant «yunohost service log {service} » o via la seccion «Servicis» de pas la pagina web dadministracion.",
"diagnosis_http_connection_error": "Error de connexion: connexion impossibla al domeni demandat, benlèu ques pas accessible.",
"diagnosis_http_unknown_error": "Una error ses producha en ensajar de se connectar a vòstre domeni, es benlèu pas accessible.",
"group_user_already_in_group": "Lutilizaire {user} es ja dins lo grop « {group} »",
"diagnosis_ip_broken_resolvconf": "La resolucion del nom de domeni sembla copada sul servidor, poiriá èsser ligada al fait que /etc/resolv.conf manda pas a 127.0.0.1.",
"diagnosis_ip_weird_resolvconf": "La resolucion del nom de domeni sembla foncionar, mas siatz prudent en utilizant un fichièr /etc/resolv.con personalizat.",
"diagnosis_diskusage_verylow": "Lo lòc demmagazinatge {mountpoint} (sul periferic {device}) a solament {free_abs_GB} Go ({free_percent}%). Deuriatz considerar de liberar un pauc despaci.",
"diagnosis_diskusage_verylow": "Lo lòc demmagazinatge {mountpoint} (sul periferic {device}) a solament {free} ({free_percent}%). Deuriatz considerar de liberar un pauc despaci.",
"global_settings_setting_pop3_enabled": "Activar lo protocòl POP3 pel servidor de corrièr",
"diagnosis_diskusage_ok": "Lo lòc demmagazinatge {mountpoint} (sul periferic {device}) a encara {free_abs_GB} Go ({free_percent}%) de liure!",
"diagnosis_swap_none": "Lo sistèma a pas cap de memòria descambi. Auriatz de considerar dajustar almens 256 Mo descambi per evitar las situacions ont lo sistèma manca de memòria.",
"diagnosis_swap_notsomuch": "Lo sistèma a solament {total_MB} de memòria descambi. Auriatz de considerar dajustar almens 256 Mo descambi per evitar las situacions ont lo sistèma manca de memòria."
"diagnosis_diskusage_ok": "Lo lòc demmagazinatge {mountpoint} (sul periferic {device}) a encara {free} ({free_percent}%) de liure!",
"diagnosis_swap_none": "Lo sistèma a pas cap de memòria descambi. Auriatz de considerar dajustar almens {recommended} descambi per evitar las situacions ont lo sistèma manca de memòria.",
"diagnosis_swap_notsomuch": "Lo sistèma a solament {total} de memòria descambi. Auriatz de considerar dajustar almens {recommended} descambi per evitar las situacions ont lo sistèma manca de memòria."
}

View file

@ -110,13 +110,39 @@ def app_catalog(full=False, with_categories=False):
return {"apps": catalog["apps"], "categories": catalog["categories"]}
def app_list(full=False):
# Old legacy function...
def app_fetchlist():
logger.warning("'yunohost app fetchlist' is deprecated. Please use 'yunohost tools update --apps' instead")
from yunohost.tools import tools_update
tools_update(apps=True)
def app_list(full=False, installed=False, filter=None):
"""
List installed apps
"""
# Old legacy argument ... app_list was a combination of app_list and
# app_catalog before 3.8 ...
if installed:
logger.warning("Argument --installed ain't needed anymore when using 'yunohost app list'. It directly returns the list of installed apps..")
# Filter is a deprecated option...
if filter:
logger.warning("Using -f $appname in 'yunohost app list' is deprecated. Just use 'yunohost app list | grep -q 'id: $appname' to check a specific app is installed")
out = []
for app_id in sorted(_installed_apps()):
if filter and not app_id.startswith(filter):
continue
try:
app_info_dict = app_info(app_id, full=full)
except Exception as e:
logger.error("Failed to read info for %s : %s" % (app_id, e))
continue
app_info_dict["id"] = app_id
out.append(app_info_dict)
@ -131,6 +157,7 @@ def app_info(app, full=False):
raise YunohostError('app_not_installed', app=app, all_apps=_get_all_installed_apps_id())
local_manifest = _get_manifest_of_app(os.path.join(APPS_SETTING_PATH, app))
settings = _get_app_settings(app)
ret = {
@ -507,7 +534,7 @@ def app_upgrade(app=[], url=None, file=None):
upgrade_failed = True if upgrade_retcode != 0 else False
if upgrade_failed:
error = m18n.n('app_upgrade_script_failed')
logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=error))
logger.error(m18n.n("app_upgrade_failed", app=app_instance_name, error=error))
failure_message_with_debug_instructions = operation_logger.error(error)
if msettings.get('interface') != 'api':
dump_app_log_extract_for_debugging(operation_logger)
@ -515,13 +542,13 @@ def app_upgrade(app=[], url=None, file=None):
except (KeyboardInterrupt, EOFError):
upgrade_retcode = -1
error = m18n.n('operation_interrupted')
logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=error))
logger.error(m18n.n("app_upgrade_failed", app=app_instance_name, error=error))
failure_message_with_debug_instructions = operation_logger.error(error)
# Something wrong happened in Yunohost's code (most probably hook_exec)
except Exception:
import traceback
error = m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())
logger.exception(m18n.n("app_install_failed", app=app_instance_name, error=error))
logger.error(m18n.n("app_install_failed", app=app_instance_name, error=error))
failure_message_with_debug_instructions = operation_logger.error(error)
finally:
# Whatever happened (install success or failure) we check if it broke the system
@ -531,7 +558,7 @@ def app_upgrade(app=[], url=None, file=None):
_assert_system_is_sane_for_app(manifest, "post")
except Exception as e:
broke_the_system = True
logger.exception(m18n.n("app_upgrade_failed", app=app_instance_name, error=str(e)))
logger.error(m18n.n("app_upgrade_failed", app=app_instance_name, error=str(e)))
failure_message_with_debug_instructions = operation_logger.error(str(e))
# If upgrade failed or broke the system,
@ -763,20 +790,20 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
install_failed = True if install_retcode != 0 else False
if install_failed:
error = m18n.n('app_install_script_failed')
logger.exception(m18n.n("app_install_failed", app=app_id, error=error))
logger.error(m18n.n("app_install_failed", app=app_id, error=error))
failure_message_with_debug_instructions = operation_logger.error(error)
if msettings.get('interface') != 'api':
dump_app_log_extract_for_debugging(operation_logger)
# Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception
except (KeyboardInterrupt, EOFError):
error = m18n.n('operation_interrupted')
logger.exception(m18n.n("app_install_failed", app=app_id, error=error))
logger.error(m18n.n("app_install_failed", app=app_id, error=error))
failure_message_with_debug_instructions = operation_logger.error(error)
# Something wrong happened in Yunohost's code (most probably hook_exec)
except Exception as e:
import traceback
error = m18n.n('unexpected_error', error=u"\n" + traceback.format_exc())
logger.exception(m18n.n("app_install_failed", app=app_id, error=error))
logger.error(m18n.n("app_install_failed", app=app_id, error=error))
failure_message_with_debug_instructions = operation_logger.error(error)
finally:
# Whatever happened (install success or failure) we check if it broke the system
@ -786,7 +813,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
_assert_system_is_sane_for_app(manifest, "post")
except Exception as e:
broke_the_system = True
logger.exception(m18n.n("app_install_failed", app=app_id, error=str(e)))
logger.error(m18n.n("app_install_failed", app=app_id, error=str(e)))
failure_message_with_debug_instructions = operation_logger.error(str(e))
# If the install failed or broke the system, we remove it
@ -823,7 +850,7 @@ def app_install(operation_logger, app, label=None, args=None, no_remove_on_failu
except (KeyboardInterrupt, EOFError, Exception):
remove_retcode = -1
import traceback
logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc()))
logger.error(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc()))
# Remove all permission in LDAP
for permission_name in user_permission_list()["permissions"].keys():
@ -994,7 +1021,7 @@ def app_remove(operation_logger, app):
except (KeyboardInterrupt, EOFError, Exception):
ret = -1
import traceback
logger.exception(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc()))
logger.error(m18n.n('unexpected_error', error=u"\n" + traceback.format_exc()))
if ret == 0:
logger.success(m18n.n('app_removed', app=app))
@ -1820,7 +1847,7 @@ def _get_app_settings(app_id):
if app_id == settings['id']:
return settings
except (IOError, TypeError, KeyError):
logger.exception(m18n.n('app_not_correctly_installed',
logger.error(m18n.n('app_not_correctly_installed',
app=app_id))
return {}
@ -2026,7 +2053,7 @@ def _get_manifest_of_app(path):
elif os.path.exists(os.path.join(path, "manifest.json")):
return read_json(os.path.join(path, "manifest.json"))
else:
return None
raise YunohostError("There doesn't seem to be any manifest file in %s ... It looks like an app was not correctly installed/removed." % path, raw_msg=True)
def _get_git_last_commit_hash(repository, reference='HEAD'):

View file

@ -35,9 +35,9 @@ import tempfile
from datetime import datetime
from glob import glob
from collections import OrderedDict
from functools import reduce
from moulinette import msignals, m18n, msettings
from yunohost.utils.error import YunohostError
from moulinette.utils import filesystem
from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml
@ -51,7 +51,8 @@ from yunohost.hook import (
from yunohost.tools import tools_postinstall
from yunohost.regenconf import regen_conf
from yunohost.log import OperationLogger
from functools import reduce
from yunohost.utils.error import YunohostError
from yunohost.utils.packages import ynh_packages_version
BACKUP_PATH = '/home/yunohost.backup'
ARCHIVES_PATH = '%s/archives' % BACKUP_PATH
@ -282,7 +283,8 @@ class BackupManager():
'size': self.size,
'size_details': self.size_details,
'apps': self.apps_return,
'system': self.system_return
'system': self.system_return,
'from_yunohost_version': ynh_packages_version()["yunohost"]["version"]
}
@property
@ -604,7 +606,7 @@ class BackupManager():
ret_succeed = {hook: [path for path, result in infos.items() if result["state"] == "succeed"]
for hook, infos in ret.items()
if any(result["state"] == "succeed" for result in infos.values())}
ret_failed = {hook: [path for path, result in infos.items.items() if result["state"] == "failed"]
ret_failed = {hook: [path for path, result in infos.items() if result["state"] == "failed"]
for hook, infos in ret.items()
if any(result["state"] == "failed" for result in infos.values())}

View file

@ -34,15 +34,14 @@ import glob
from datetime import datetime
from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate
from yunohost.utils.error import YunohostError
from moulinette import m18n
from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_file
from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate
from yunohost.utils.error import YunohostError
from yunohost.utils.network import get_public_ip
from moulinette import m18n
from yunohost.app import app_ssowatconf
from yunohost.service import _run_service_command
from yunohost.regenconf import regen_conf
from yunohost.log import OperationLogger
@ -285,7 +284,6 @@ def _certificate_install_letsencrypt(domain_list, force=False, no_checks=False,
operation_logger.start()
_configure_for_acme_challenge(domain)
_fetch_and_enable_new_certificate(domain, staging, no_checks=no_checks)
_install_cron(no_checks=no_checks)
@ -468,61 +466,16 @@ Subject: %s
smtp.quit()
def _configure_for_acme_challenge(domain):
nginx_conf_folder = "/etc/nginx/conf.d/%s.d" % domain
nginx_conf_file = "%s/000-acmechallenge.conf" % nginx_conf_folder
nginx_configuration = '''
location ^~ '/.well-known/acme-challenge/'
{
default_type "text/plain";
alias %s;
}
''' % WEBROOT_FOLDER
# Check there isn't a conflicting file for the acme-challenge well-known
# uri
for path in glob.glob('%s/*.conf' % nginx_conf_folder):
if path == nginx_conf_file:
continue
with open(path) as f:
contents = f.read()
if '/.well-known/acme-challenge' in contents:
raise YunohostError('certmanager_conflicting_nginx_file', filepath=path)
# Write the conf
if os.path.exists(nginx_conf_file):
logger.debug(
"Nginx configuration file for ACME challenge already exists for domain, skipping.")
return
logger.debug(
"Adding Nginx configuration file for Acme challenge for domain %s.", domain)
with open(nginx_conf_file, "w") as f:
f.write(nginx_configuration)
# Assume nginx conf is okay, and reload it
# (FIXME : maybe add a check that it is, using nginx -t, haven't found
# any clean function already implemented in yunohost to do this though)
_run_service_command("reload", "nginx")
app_ssowatconf()
def _check_acme_challenge_configuration(domain):
# Check nginx conf file exists
nginx_conf_folder = "/etc/nginx/conf.d/%s.d" % domain
nginx_conf_file = "%s/000-acmechallenge.conf" % nginx_conf_folder
if not os.path.exists(nginx_conf_file):
return False
else:
domain_conf = "/etc/nginx/conf.d/%s.conf" % domain
if "include /etc/nginx/conf.d/acme-challenge.conf.inc" in read_file(domain_conf):
return True
else:
# This is for legacy setups which haven't updated their domain conf to
# the new conf that include the acme snippet...
legacy_acme_conf = "/etc/nginx/conf.d/%s.d/000-acmechallenge.conf" % domain
return os.path.exists(legacy_acme_conf)
def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False):

View file

@ -24,6 +24,7 @@
Look for possible issues on the server
"""
import re
import os
import time
@ -38,14 +39,38 @@ logger = log.getActionLogger('yunohost.diagnosis')
DIAGNOSIS_CACHE = "/var/cache/yunohost/diagnosis/"
DIAGNOSIS_CONFIG_FILE = '/etc/yunohost/diagnosis.yml'
DIAGNOSIS_SERVER = "diagnosis.yunohost.org"
def diagnosis_list():
all_categories_names = [h for h, _ in _list_diagnosis_categories()]
return {"categories": all_categories_names}
def diagnosis_get(category, item):
# Get all the categories
all_categories = _list_diagnosis_categories()
all_categories_names = [c for c, _ in all_categories]
if category not in all_categories_names:
raise YunohostError('diagnosis_unknown_categories', categories=category)
if isinstance(item, list):
if any("=" not in criteria for criteria in item):
raise YunohostError("Criterias should be of the form key=value (e.g. domain=yolo.test)")
# Convert the provided criteria into a nice dict
item = {c.split("=")[0]: c.split("=")[1] for c in item}
return Diagnoser.get_cached_report(category, item=item)
def diagnosis_show(categories=[], issues=False, full=False, share=False):
if not os.path.exists(DIAGNOSIS_CACHE):
logger.warning(m18n.n("diagnosis_never_ran_yet"))
return
# Get all the categories
all_categories = _list_diagnosis_categories()
all_categories_names = [category for category, _ in all_categories]
@ -56,29 +81,20 @@ def diagnosis_show(categories=[], issues=False, full=False, share=False):
else:
unknown_categories = [c for c in categories if c not in all_categories_names]
if unknown_categories:
raise YunohostError('diagnosis_unknown_categories', categories=", ".join(categories))
if not os.path.exists(DIAGNOSIS_CACHE):
logger.warning(m18n.n("diagnosis_never_ran_yet"))
return
raise YunohostError('diagnosis_unknown_categories', categories=", ".join(unknown_categories))
# Fetch all reports
all_reports = []
for category in categories:
if not os.path.exists(Diagnoser.cache_file(category)):
logger.warning(m18n.n("diagnosis_no_cache", category=category))
report = {"id": category,
"cached_for": -1,
"timestamp": -1,
"items": []}
Diagnoser.i18n(report)
else:
try:
report = Diagnoser.get_cached_report(category)
except Exception as e:
logger.error(m18n.n("diagnosis_failed", category=category, error=str(e)))
continue
Diagnoser.i18n(report)
add_ignore_flag_to_issues(report)
if not full:
del report["timestamp"]
@ -128,7 +144,10 @@ def _dump_human_readable_reports(reports):
return(output)
def diagnosis_run(categories=[], force=False):
def diagnosis_run(categories=[], force=False, except_if_never_ran_yet=False):
if except_if_never_ran_yet and not os.path.exists(DIAGNOSIS_CACHE):
return
# Get all the categories
all_categories = _list_diagnosis_categories()
@ -152,17 +171,15 @@ def diagnosis_run(categories=[], force=False):
try:
code, report = hook_exec(path, args={"force": force}, env=None)
except Exception as e:
logger.error(m18n.n("diagnosis_failed_for_category", category=category, error=str(e)), exc_info=True)
import traceback
logger.error(m18n.n("diagnosis_failed_for_category", category=category, error='\n'+traceback.format_exc()))
else:
diagnosed_categories.append(category)
if report != {}:
issues.extend([item for item in report["items"] if item["status"] in ["WARNING", "ERROR"]])
if issues:
if msettings.get("interface") == "api":
logger.info(m18n.n("diagnosis_display_tip_web"))
else:
logger.info(m18n.n("diagnosis_display_tip_cli"))
if issues and msettings.get("interface") == "cli":
logger.warning(m18n.n("diagnosis_display_tip"))
return
@ -221,7 +238,7 @@ def diagnosis_ignore(add_filter=None, remove_filter=None, list=False):
if category not in all_categories_names:
raise YunohostError("%s is not a diagnosis category" % category)
if any("=" not in criteria for criteria in filter_[1:]):
raise YunohostError("Extra criterias should be of the form key=value (e.g. domain=yolo.test)")
raise YunohostError("Criterias should be of the form key=value (e.g. domain=yolo.test)")
# Convert the provided criteria into a nice dict
criterias = {c.split("=")[0]: c.split("=")[1] for c in filter_[1:]}
@ -356,15 +373,22 @@ class Diagnoser():
for dependency in self.dependencies:
dep_report = Diagnoser.get_cached_report(dependency)
if dep_report["timestamp"] == -1: # No cache yet for this dep
dep_errors = True
else:
dep_errors = [item for item in dep_report["items"] if item["status"] == "ERROR"]
if dep_errors:
logger.error(m18n.n("diagnosis_cant_run_because_of_dep", category=self.description, dep=Diagnoser.get_description(dependency)))
return 1, {}
self.logger_debug("Running diagnostic for %s" % self.id_)
items = list(self.run())
for item in items:
if "details" in item and not item["details"]:
del item["details"]
new_report = {"id": self.id_,
"cached_for": self.cache_duration,
"items": items}
@ -396,11 +420,24 @@ class Diagnoser():
return os.path.join(DIAGNOSIS_CACHE, "%s.json" % id_)
@staticmethod
def get_cached_report(id_):
filename = Diagnoser.cache_file(id_)
report = read_json(filename)
report["timestamp"] = int(os.path.getmtime(filename))
Diagnoser.i18n(report)
def get_cached_report(id_, item=None):
cache_file = Diagnoser.cache_file(id_)
if not os.path.exists(cache_file):
logger.warning(m18n.n("diagnosis_no_cache", category=id_))
report = {"id": id_,
"cached_for": -1,
"timestamp": -1,
"items": []}
else:
report = read_json(cache_file)
report["timestamp"] = int(os.path.getmtime(cache_file))
if item:
for report_item in report["items"]:
if report_item.get("meta") == item:
return report_item
return {}
else:
return report
@staticmethod
@ -422,11 +459,84 @@ class Diagnoser():
report["description"] = Diagnoser.get_description(report["id"])
for item in report["items"]:
summary_key, summary_args = item["summary"]
item["summary"] = m18n.n(summary_key, **summary_args)
# For the summary and each details, we want to call
# m18n() on the string, with the appropriate data for string
# formatting which can come from :
# - infos super-specific to the summary/details (if it's a tuple(key,dict_with_info) and not just a string)
# - 'meta' info = parameters of the test (e.g. which domain/category for DNS conf record)
# - actual 'data' retrieved from the test (e.g. actual global IP, ...)
meta_data = item.get("meta", {}).copy()
meta_data.update(item.get("data", {}))
html_tags = re.compile(r'<[^>]+>')
def m18n_(info):
if not isinstance(info, tuple) and not isinstance(info, list):
info = (info, {})
info[1].update(meta_data)
s = m18n.n(info[0], **(info[1]))
# In cli, we remove the html tags
if msettings.get("interface") != "api":
s = s.replace("<cmd>", "'").replace("</cmd>", "'")
s = html_tags.sub('', s.replace("<br>","\n"))
else:
s = s.replace("<cmd>", "<code class='cmd'>").replace("</cmd>", "</code>")
# Make it so that links open in new tabs
s = s.replace("<a href=", "<a target='_blank' rel='noopener noreferrer' href=")
return s
item["summary"] = m18n_(item["summary"])
if "details" in item:
item["details"] = [m18n.n(key, *values) for key, values in item["details"]]
item["details"] = [m18n_(info) for info in item["details"]]
@staticmethod
def remote_diagnosis(uri, data, ipversion, timeout=30):
# Lazy loading for performance
import requests
import socket
# Monkey patch socket.getaddrinfo to force request() to happen in ipv4
# or 6 ...
# Inspired by https://stackoverflow.com/a/50044152
old_getaddrinfo = socket.getaddrinfo
def getaddrinfo_ipv4_only(*args, **kwargs):
responses = old_getaddrinfo(*args, **kwargs)
return [response
for response in responses
if response[0] == socket.AF_INET]
def getaddrinfo_ipv6_only(*args, **kwargs):
responses = old_getaddrinfo(*args, **kwargs)
return [response
for response in responses
if response[0] == socket.AF_INET6]
if ipversion == 4:
socket.getaddrinfo = getaddrinfo_ipv4_only
elif ipversion == 6:
socket.getaddrinfo = getaddrinfo_ipv6_only
url = 'https://%s/%s' % (DIAGNOSIS_SERVER, uri)
try:
r = requests.post(url, json=data, timeout=timeout)
finally:
socket.getaddrinfo = old_getaddrinfo
if r.status_code not in [200, 400]:
raise Exception("The remote diagnosis server failed miserably while trying to diagnose your server. This is most likely an error on Yunohost's infrastructure and not on your side. Please contact the YunoHost team an provide them with the following information.<br>URL: <code>%s</code><br>Status code: <code>%s</code>" % (url, r.status_code))
if r.status_code == 400:
raise Exception("Diagnosis request was refused: %s" % r.content)
try:
r = r.json()
except Exception as e:
raise Exception("Failed to parse json from diagnosis server response.\nError: %s\nOriginal content: %s" % (e, r.content))
return r
def _list_diagnosis_categories():

View file

@ -397,7 +397,7 @@ def _normalize_domain_path(domain, path):
return domain, path
def _build_dns_conf(domain, ttl=3600):
def _build_dns_conf(domain, ttl=3600, include_empty_AAAA_if_no_ipv6=False):
"""
Internal function that will returns a data structure containing the needed
information to generate/adapt the dns configuration
@ -450,21 +450,16 @@ def _build_dns_conf(domain, ttl=3600):
if ipv6:
basic.append(["@", ttl, "AAAA", ipv6])
elif include_empty_AAAA_if_no_ipv6:
basic.append(["@", ttl, "AAAA", None])
#########
# Email #
#########
spf_record = '"v=spf1 a mx'
if ipv4:
spf_record += ' ip4:{ip4}'.format(ip4=ipv4)
if ipv6:
spf_record += ' ip6:{ip6}'.format(ip6=ipv6)
spf_record += ' -all"'
mail = [
["@", ttl, "MX", "10 %s." % domain],
["@", ttl, "TXT", spf_record],
["@", ttl, "TXT", '"v=spf1 a mx -all"'],
]
# DKIM/DMARC record
@ -497,8 +492,11 @@ def _build_dns_conf(domain, ttl=3600):
if ipv4:
extra.append(["*", ttl, "A", ipv4])
if ipv6:
extra.append(["*", ttl, "AAAA", ipv6])
elif include_empty_AAAA_if_no_ipv6:
extra.append(["*", ttl, "AAAA", None])
extra.append(["@", ttl, "CAA", '128 issue "letsencrypt.org"'])

View file

@ -259,11 +259,6 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
dns_conf = _build_dns_conf(domain)
for i, record in enumerate(dns_conf["extra"]):
# Ignore CAA record ... not sure why, we could probably enforce it...
if record[3] == "CAA":
del dns_conf["extra"][i]
# Delete custom DNS records, we don't support them (have to explicitly
# authorize them on dynette)
for category in dns_conf.keys():

View file

@ -196,6 +196,28 @@ def user_permission_reset(operation_logger, permission, sync_perm=True):
return new_permission
def user_permission_info(permission):
"""
Return informations about a specific permission
Keyword argument:
permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
"""
# By default, manipulate main permission
if "." not in permission:
permission = permission + ".main"
# Fetch existing permission
existing_permission = user_permission_list(full=True)["permissions"].get(permission, None)
if existing_permission is None:
raise YunohostError('permission_not_found', permission=permission)
return existing_permission
#
#
# The followings methods are *not* directly exposed.

View file

@ -80,7 +80,7 @@ def service_add(name, description=None, log=None, log_type="file", test_status=N
services[name]['description'] = description
else:
# Try to get the description from systemd service
out = subprocess.check_output("systemctl show %s | grep '^Description='" % name, shell=True)
out = subprocess.check_output("systemctl show %s | grep '^Description='" % name, shell=True).strip()
out = out.replace("Description=", "")
# If the service does not yet exists or if the description is empty,
# systemd will anyway return foo.service as default value, so we wanna
@ -295,16 +295,11 @@ def service_status(names=[]):
if services[name].get("status", "") is None:
continue
status = _get_service_information_from_systemd(name)
# try to get status using alternative version if they exists
# this is for mariadb/mysql but is generic in case of
alternates = services[name].get("alternates", [])
while status is None and alternates:
status = _get_service_information_from_systemd(alternates.pop())
systemd_service = services[name].get("actual_systemd_service", name)
status = _get_service_information_from_systemd(systemd_service)
if status is None:
logger.error("Failed to get status information via dbus for service %s, systemctl didn't recognize this service ('NoSuchUnit')." % name)
logger.error("Failed to get status information via dbus for service %s, systemctl didn't recognize this service ('NoSuchUnit')." % systemd_service)
result[name] = {
'status': "unknown",
'start_on_boot': "unknown",
@ -338,6 +333,8 @@ def service_status(names=[]):
# gotta do this ... cf code of /lib/systemd/systemd-sysv-install
if result[name]["start_on_boot"] == "generated":
result[name]["start_on_boot"] = "enabled" if glob("/etc/rc[S5].d/S??"+name) else "disabled"
elif os.path.exists("/etc/systemd/system/multi-user.target.wants/%s.service" % name):
result[name]["start_on_boot"] = "enabled"
if "StateChangeTimestamp" in status:
result[name]['last_state_change'] = datetime.utcfromtimestamp(status["StateChangeTimestamp"] / 1000000)
@ -408,6 +405,7 @@ def service_log(name, number=50):
"""
services = _get_services()
number = int(number)
if name not in services.keys():
raise YunohostError('service_unknown', service=name)
@ -423,11 +421,7 @@ def service_log(name, number=50):
result = {}
# First we always add the logs from journalctl / systemd
result["journalctl"] = _get_journalctl_logs(name, int(number)).splitlines()
# Mysql and journalctl are fucking annoying, we gotta explictly fetch mariadb ...
if name == "mysql":
result["journalctl"] = _get_journalctl_logs("mariadb", int(number)).splitlines()
result["journalctl"] = _get_journalctl_logs(name, number).splitlines()
for index, log_path in enumerate(log_list):
log_type = log_type_list[index]
@ -435,7 +429,7 @@ def service_log(name, number=50):
if log_type == "file":
# log is a file, read it
if not os.path.isdir(log_path):
result[log_path] = _tail(log_path, int(number)) if os.path.exists(log_path) else []
result[log_path] = _tail(log_path, number) if os.path.exists(log_path) else []
continue
for log_file in os.listdir(log_path):
@ -447,10 +441,11 @@ def service_log(name, number=50):
if not log_file.endswith(".log"):
continue
result[log_file_path] = _tail(log_file_path, int(number)) if os.path.exists(log_file_path) else []
result[log_file_path] = _tail(log_file_path, number) if os.path.exists(log_file_path) else []
else:
# N.B. : this is legacy code that can probably be removed ... to be confirmed
# get log with journalctl
result[log_path] = _get_journalctl_logs(log_path, int(number)).splitlines()
result[log_path] = _get_journalctl_logs(log_path, number).splitlines()
return result
@ -572,13 +567,21 @@ def _get_services():
services = yaml.load(f)
except:
return {}
else:
# some services are marked as None to remove them from YunoHost
# filter this
for key, value in services.items():
if value is None:
del services[key]
# Stupid hack for postgresql which ain't an official service ... Can't
# really inject that info otherwise. Real service we want to check for
# status and log is in fact postgresql@x.y-main (x.y being the version)
if "postgresql" in services:
if "description" in services["postgresql"]:
del services["postgresql"]["description"]
services["postgresql"]["actual_systemd_service"] = "postgresql@9.6-main"
return services
@ -674,8 +677,10 @@ def _find_previous_log_file(file):
def _get_journalctl_logs(service, number="all"):
services = _get_services()
systemd_service = services.get(service, {}).get("actual_systemd_service", service)
try:
return subprocess.check_output("journalctl -xn -u {0} -n{1}".format(service, number), shell=True)
return subprocess.check_output("journalctl -xn -u {0} -n{1}".format(systemd_service, number), shell=True)
except:
import traceback
return "error while get services logs from journalctl:\n%s" % traceback.format_exc()

View file

@ -70,6 +70,7 @@ DEFAULTS = OrderedDict([
("security.postfix.compatibility", {"type": "enum", "default": "intermediate",
"choices": ["intermediate", "modern"]}),
("pop3.enabled", {"type": "bool", "default": False}),
("smtp.allow_ipv6", {"type": "bool", "default": True}),
])
@ -320,6 +321,7 @@ def reconfigure_ssh(setting_name, old_value, new_value):
if old_value != new_value:
service_regen_conf(names=['ssh'])
@post_change_hook("smtp.allow_ipv6")
@post_change_hook("security.postfix.compatibility")
def reconfigure_postfix(setting_name, old_value, new_value):
if old_value != new_value:

View file

@ -43,7 +43,7 @@ from yunohost.dyndns import _dyndns_available, _dyndns_provides
from yunohost.firewall import firewall_upnp
from yunohost.service import service_start, service_enable
from yunohost.regenconf import regen_conf
from yunohost.utils.packages import _dump_sources_list, _list_upgradable_apt_packages
from yunohost.utils.packages import _dump_sources_list, _list_upgradable_apt_packages, ynh_packages_version
from yunohost.utils.error import YunohostError
from yunohost.log import is_unit_operation, OperationLogger
@ -53,6 +53,8 @@ MIGRATIONS_STATE_PATH = "/etc/yunohost/migrations.yaml"
logger = getActionLogger('yunohost.tools')
def tools_versions():
return ynh_packages_version()
def tools_ldapinit():
"""
@ -316,7 +318,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
'touch %s/index.txt' % ssl_dir,
'cp %s/openssl.cnf %s/openssl.ca.cnf' % (ssl_dir, ssl_dir),
'sed -i s/yunohost.org/%s/g %s/openssl.ca.cnf ' % (domain, ssl_dir),
'openssl req -x509 -new -config %s/openssl.ca.cnf -days 3650 -out %s/ca/cacert.pem -keyout %s/ca/cakey.pem -nodes -batch' % (ssl_dir, ssl_dir, ssl_dir),
'openssl req -x509 -new -config %s/openssl.ca.cnf -days 3650 -out %s/ca/cacert.pem -keyout %s/ca/cakey.pem -nodes -batch -subj /CN=%s/O=%s' % (ssl_dir, ssl_dir, ssl_dir, domain, os.path.splitext(domain)[0]),
'cp %s/ca/cacert.pem /etc/ssl/certs/ca-yunohost_crt.pem' % ssl_dir,
'update-ca-certificates'
]

View file

@ -165,12 +165,13 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
operation_logger.start()
# Get random UID/GID
all_uid = {x.pw_uid for x in pwd.getpwall()}
all_gid = {x.gr_gid for x in grp.getgrall()}
all_uid = {str(x.pw_uid) for x in pwd.getpwall()}
all_gid = {str(x.gr_gid) for x in grp.getgrall()}
uid_guid_found = False
while not uid_guid_found:
uid = str(random.randint(200, 99999))
# LXC uid number is limited to 65536 by default
uid = str(random.randint(200, 65000))
uid_guid_found = uid not in all_uid and uid not in all_gid
# Adapt values for LDAP
@ -201,8 +202,9 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
except Exception as e:
raise YunohostError('user_creation_failed', user=username, error=e)
# Invalidate passwd to take user creation into account
# Invalidate passwd and group to take user and group creation into account
subprocess.call(['nscd', '-i', 'passwd'])
subprocess.call(['nscd', '-i', 'group'])
try:
# Attempt to create user home folder
@ -780,6 +782,11 @@ def user_permission_reset(permission, sync_perm=True):
sync_perm=sync_perm)
def user_permission_info(permission):
import yunohost.permission
return yunohost.permission.user_permission_info(permission)
#
# SSH subcategory
#

View file

@ -18,10 +18,14 @@
along with this program; if not, see http://www.gnu.org/licenses
"""
import logging
import os
import re
import subprocess
import logging
import dns.resolver
from moulinette.utils.network import download_text
from moulinette.utils.process import check_output
from moulinette.utils.filesystem import read_file
logger = logging.getLogger('yunohost.utils.network')
@ -36,6 +40,17 @@ def get_public_ip(protocol=4):
else:
raise ValueError("invalid protocol version")
# We can know that ipv6 is not available directly if this file does not exists
if protocol == 6 and not os.path.exists("/proc/net/if_inet6"):
logger.debug("IPv6 appears not at all available on the system, so assuming there's no IP address for that version")
return None
# If we are indeed connected in ipv4 or ipv6, we should find a default route
routes = check_output("ip -%s route" % protocol).split("\n")
if not any(r.startswith("default") for r in routes):
logger.debug("No default route for IPv%s, so assuming there's no IP address for that version" % protocol)
return None
try:
return download_text(url, timeout=30).strip()
except Exception as e:
@ -47,7 +62,7 @@ def get_network_interfaces():
# Get network devices and their addresses (raw infos from 'ip addr')
devices_raw = {}
output = subprocess.check_output('ip addr show'.split())
output = check_output('ip addr show')
for d in re.split(r'^(?:[0-9]+: )', output, flags=re.MULTILINE):
# Extract device name (1) and its addresses (2)
m = re.match(r'([^\s@]+)(?:@[\S]+)?: (.*)', d, flags=re.DOTALL)
@ -62,7 +77,7 @@ def get_network_interfaces():
def get_gateway():
output = subprocess.check_output('ip route show'.split())
output = check_output('ip route show')
m = re.search(r'default via (.*) dev ([a-z]+[0-9]?)', output)
if not m:
return None
@ -71,6 +86,52 @@ def get_gateway():
return addr.popitem()[1] if len(addr) == 1 else None
# Lazy dev caching to avoid re-reading the file multiple time when calling
# dig() often during same yunohost operation
external_resolvers_ = []
def external_resolvers():
global external_resolvers_
if not external_resolvers_:
resolv_dnsmasq_conf = read_file("/etc/resolv.dnsmasq.conf").split("\n")
external_resolvers_ = [r.split(" ")[1] for r in resolv_dnsmasq_conf if r.startswith("nameserver")]
return external_resolvers_
def dig(qname, rdtype="A", timeout=5, resolvers="local", edns_size=1500, full_answers=False):
"""
Do a quick DNS request and avoid the "search" trap inside /etc/resolv.conf
"""
if resolvers == "local":
resolvers = ["127.0.0.1"]
elif resolvers == "force_external":
resolvers = external_resolvers()
else:
assert isinstance(resolvers, list)
resolver = dns.resolver.Resolver(configure=False)
resolver.use_edns(0, 0, edns_size)
resolver.nameservers = resolvers
resolver.timeout = timeout
try:
answers = resolver.query(qname, rdtype)
except (dns.resolver.NXDOMAIN,
dns.resolver.NoNameservers,
dns.resolver.NoAnswer,
dns.exception.Timeout) as e:
return ("nok", (e.__class__.__name__, e))
if not full_answers:
answers = [answer.to_text() for answer in answers]
return ("ok", answers)
def _extract_inet(string, skip_netmask=False, skip_loopback=True):
"""
Extract IP addresses (v4 and/or v6) from a string limited to one

View file

@ -37,10 +37,18 @@ def yunopaste(data):
def anonymize(data):
def anonymize_domain(data, domain, redact):
data = data.replace(domain, redact)
# This stuff appears sometimes because some folder in
# /var/lib/metronome/ have some folders named this way
data = data.replace(domain.replace(".", "%2e"), redact.replace(".", "%2e"))
return data
# First, let's replace every occurence of the main domain by "domain.tld"
# This should cover a good fraction of the info leaked
main_domain = _get_maindomain()
data = data.replace(main_domain, "maindomain.tld")
data = anonymize_domain(data, main_domain, "maindomain.tld")
# Next, let's replace other domains. We do this in increasing lengths,
# because e.g. knowing that the domain is a sub-domain of another domain may
@ -55,7 +63,7 @@ def anonymize(data):
for domain in domains:
if domain not in data:
continue
data = data.replace(domain, "domain%s.tld" % count)
data = anonymize_domain(data, domain, "domain%s.tld" % count)
count += 1
# We also want to anonymize the ips

View file

@ -49,6 +49,9 @@ def find_expected_string_keys():
for python_file in glob.glob("data/hooks/diagnosis/*.py"):
content = open(python_file).read()
for m in p3.findall(content):
if m.endswith("_"):
# Ignore some name fragments which are actually concatenated with other stuff..
continue
yield m
yield "diagnosis_description_" + os.path.basename(python_file)[:-3].split("-")[-1]
@ -123,6 +126,13 @@ def find_expected_string_keys():
for i in [1, 2, 3, 4]:
yield "password_too_simple_%s" % i
checks = ["outgoing_port_25_ok", "ehlo_ok", "fcrdns_ok",
"blacklist_ok", "queue_ok", "ehlo_bad_answer",
"ehlo_unreachable", "ehlo_bad_answer_details",
"ehlo_unreachable_details", ]
for check in checks:
yield "diagnosis_mail_%s" % check
###############################################################################
# Load en locale json keys #
###############################################################################