mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge branch 'stretch-unstable' into use_getops
This commit is contained in:
commit
4bac17a162
68 changed files with 2289 additions and 1350 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -32,3 +32,6 @@ pip-log.txt
|
|||
|
||||
# moulinette lib
|
||||
src/yunohost/locales
|
||||
|
||||
# Test
|
||||
src/yunohost/tests/apps
|
||||
|
|
|
@ -26,7 +26,7 @@ This repository is the core of YunoHost code.
|
|||
- [Modules for the XMPP server Metronome](https://github.com/YunoHost/yunohost/tree/stable/lib/metronome/modules).
|
||||
- [Debian files](https://github.com/YunoHost/yunohost/tree/stable/debian) for package creation.
|
||||
|
||||
## How does it works?
|
||||
## How does it work?
|
||||
- Python core scripts are accessible through two interfaces thanks to the [moulinette framework](https://github.com/YunoHost/moulinette):
|
||||
- [CLI](https://en.wikipedia.org/wiki/Command-line_interface) for `yunohost` command.
|
||||
- [API](https://en.wikipedia.org/wiki/Application_programming_interface) for [web administration module](https://github.com/YunoHost/yunohost-admin) (other modules could be implemented).
|
||||
|
|
|
@ -13,7 +13,8 @@ DEFAULT_HOST = 'localhost'
|
|||
DEFAULT_PORT = 6787
|
||||
|
||||
# Level for which loggers will log
|
||||
LOGGERS_LEVEL = 'INFO'
|
||||
LOGGERS_LEVEL = 'DEBUG'
|
||||
API_LOGGER_LEVEL = 'INFO'
|
||||
|
||||
# Handlers that will be used by loggers
|
||||
# - file: log to the file LOG_DIR/LOG_FILE
|
||||
|
@ -97,8 +98,10 @@ def _init_moulinette(use_websocket=True, debug=False, verbose=False):
|
|||
|
||||
# Define loggers level
|
||||
level = LOGGERS_LEVEL
|
||||
api_level = API_LOGGER_LEVEL
|
||||
if debug:
|
||||
level = 'DEBUG'
|
||||
api_level = 'DEBUG'
|
||||
|
||||
# Custom logging configuration
|
||||
logging = {
|
||||
|
@ -119,6 +122,7 @@ def _init_moulinette(use_websocket=True, debug=False, verbose=False):
|
|||
},
|
||||
'handlers': {
|
||||
'api': {
|
||||
'level': api_level,
|
||||
'class': 'moulinette.interfaces.api.APIQueueHandler',
|
||||
},
|
||||
'file': {
|
||||
|
|
|
@ -5,7 +5,7 @@ ip=$(hostname --all-ip-address)
|
|||
|
||||
# Fetch SSH fingerprints
|
||||
i=0
|
||||
for key in /etc/ssh/ssh_host_*_key.pub ; do
|
||||
for key in $(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key.pub 2> /dev/null) ; do
|
||||
output=$(ssh-keygen -l -f $key)
|
||||
fingerprint[$i]=" - $(echo $output | cut -d' ' -f2) $(echo $output| cut -d' ' -f4)"
|
||||
i=$(($i + 1))
|
||||
|
|
|
@ -105,7 +105,7 @@ user:
|
|||
ask: ask_lastname
|
||||
required: True
|
||||
pattern: &pattern_lastname
|
||||
- !!str ^([^\W\d_]{2,30}[ ,.']{0,3})+$
|
||||
- !!str ^([^\W\d_]{2,30}[ ,.'-]{0,3})+$
|
||||
- "pattern_lastname"
|
||||
-m:
|
||||
full: --mail
|
||||
|
@ -125,6 +125,7 @@ user:
|
|||
pattern: &pattern_password
|
||||
- !!str ^.{3,}$
|
||||
- "pattern_password"
|
||||
comment: good_practices_about_user_password
|
||||
-q:
|
||||
full: --mailbox-quota
|
||||
help: Mailbox size quota
|
||||
|
@ -147,6 +148,7 @@ user:
|
|||
extra:
|
||||
pattern: *pattern_username
|
||||
--purge:
|
||||
help: Purge user's home and mail directories
|
||||
action: store_true
|
||||
|
||||
### user_update()
|
||||
|
@ -556,6 +558,10 @@ app:
|
|||
full: --no-remove-on-failure
|
||||
help: Debug option to avoid removing the app on a failed installation
|
||||
action: store_true
|
||||
-f:
|
||||
full: --force
|
||||
help: Do not ask confirmation if the app is not safe to use (low quality, experimental or 3rd party)
|
||||
action: store_true
|
||||
|
||||
### app_remove() TODO: Write help
|
||||
remove:
|
||||
|
@ -1445,7 +1451,7 @@ tools:
|
|||
|
||||
### tools_adminpw()
|
||||
adminpw:
|
||||
action_help: Change admin password
|
||||
action_help: Change password of admin and root users
|
||||
api: PUT /adminpw
|
||||
configuration:
|
||||
authenticate: all
|
||||
|
@ -1456,6 +1462,7 @@ tools:
|
|||
password: ask_new_admin_password
|
||||
pattern: *pattern_password
|
||||
required: True
|
||||
comment: good_practices_about_admin_password
|
||||
|
||||
### tools_maindomain()
|
||||
maindomain:
|
||||
|
@ -1493,9 +1500,13 @@ tools:
|
|||
password: ask_new_admin_password
|
||||
pattern: *pattern_password
|
||||
required: True
|
||||
comment: good_practices_about_admin_password
|
||||
--ignore-dyndns:
|
||||
help: Do not subscribe domain to a DynDNS service
|
||||
action: store_true
|
||||
--force-password:
|
||||
help: Use this if you really want to set a weak password
|
||||
action: store_true
|
||||
|
||||
### tools_update()
|
||||
update:
|
||||
|
|
|
@ -260,11 +260,7 @@ ynh_add_fpm_config () {
|
|||
|
||||
if [ -e "../conf/php-fpm.ini" ]
|
||||
then
|
||||
finalphpini="$fpm_config_dir/conf.d/20-$app.ini"
|
||||
ynh_backup_if_checksum_is_different --file="$finalphpini"
|
||||
sudo cp ../conf/php-fpm.ini "$finalphpini"
|
||||
sudo chown root: "$finalphpini"
|
||||
ynh_store_file_checksum --file="$finalphpini"
|
||||
echo "Please do not use a separate ini file, merge you directives in the pool file instead." &>2
|
||||
fi
|
||||
sudo systemctl reload $fpm_service
|
||||
}
|
||||
|
@ -275,10 +271,10 @@ 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 5 if not set
|
||||
# Assume php version 7 if not set
|
||||
if [ -z "$fpm_config_dir" ]; then
|
||||
fpm_config_dir="/etc/php5/fpm"
|
||||
fpm_service="php5-fpm"
|
||||
fpm_config_dir="/etc/php/7.0/fpm"
|
||||
fpm_service="php7.0-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
|
||||
|
|
|
@ -10,12 +10,14 @@ CAN_BIND=${CAN_BIND:-1}
|
|||
#
|
||||
# If DEST is ended by a slash it complete this path with the basename of SRC.
|
||||
#
|
||||
# usage: ynh_backup --src_path=src_path [--dest_path=dest_path] [--is_big]
|
||||
# usage: ynh_backup --src_path=src_path [--dest_path=dest_path] [--is_big] [--not_mandatory]
|
||||
# | arg: -s, --src_path - file or directory to bind or symlink or copy. it shouldn't be in
|
||||
# the backup dir.
|
||||
# | arg: -d, --dest_path - destination file or directory inside the
|
||||
# backup dir
|
||||
# | arg: -b, --is_big - Indicate data are big (mail, video, image ...)
|
||||
# | arg: -m, --not_mandatory - Indicate that if the file is missing, the backup can ignore it.
|
||||
# | arg: arg - Deprecated arg
|
||||
#
|
||||
# example:
|
||||
# # Wordpress app context
|
||||
|
@ -44,14 +46,16 @@ ynh_backup() {
|
|||
# TODO find a way to avoid injection by file strange naming !
|
||||
|
||||
# Declare an array to define the options of this helper.
|
||||
declare -Ar args_array=( [s]=src_path= [d]=dest_path= [b]=is_big )
|
||||
declare -Ar args_array=( [s]=src_path= [d]=dest_path= [b]=is_big [m]=not_mandatory )
|
||||
local src_path
|
||||
local dest_path
|
||||
local is_big
|
||||
local not_mandatory
|
||||
# Manage arguments with getopts
|
||||
ynh_handle_getopts_args "$@"
|
||||
local dest_path="${dest_path:-}"
|
||||
local is_big="${is_big:-0}"
|
||||
local not_mandatory="${not_mandatory:-0}"
|
||||
|
||||
BACKUP_CORE_ONLY=${BACKUP_CORE_ONLY:-0}
|
||||
|
||||
|
@ -68,7 +72,19 @@ ynh_backup() {
|
|||
# Be sure the source path is not empty
|
||||
[[ -e "${src_path}" ]] || {
|
||||
echo "Source path '${src_path}' does not exist" >&2
|
||||
return 1
|
||||
if [ "$not_mandatory" == "0" ]
|
||||
then
|
||||
# This is a temporary fix for fail2ban config files missing after the migration to stretch.
|
||||
if echo "${src_path}" | grep --quiet "/etc/fail2ban"
|
||||
then
|
||||
touch "${src_path}"
|
||||
echo "The missing file will be replaced by a dummy one for the backup !!!" >&2
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Transform the source path as an absolute path
|
||||
|
@ -175,12 +191,13 @@ with open(sys.argv[1], 'r') as backup_file:
|
|||
# Use the registered path in backup_list by ynh_backup to restore the file at
|
||||
# the good place.
|
||||
#
|
||||
# usage: ynh_restore_file --origin_path=origin_path [--dest_path=dest_path]
|
||||
# usage: ynh_restore_file --origin_path=origin_path [--dest_path=dest_path] [--not_mandatory]
|
||||
# | arg: -o, --origin_path - Path where was located the file or the directory before
|
||||
# to be backuped or relative path to $YNH_CWD where it is located in the backup archive
|
||||
# | arg: -d, --dest_path - Path where restore the file or the dir, if unspecified,
|
||||
# the destination will be ORIGIN_PATH or if the ORIGIN_PATH doesn't exist in
|
||||
# the archive, the destination will be searched into backup.csv
|
||||
# | arg: -m, --not_mandatory - Indicate that if the file is missing, the restore process can ignore it.
|
||||
#
|
||||
# If DEST_PATH already exists and is lighter than 500 Mo, a backup will be made in
|
||||
# /home/yunohost.conf/backup/. Otherwise, the existing file is removed.
|
||||
|
@ -197,20 +214,27 @@ with open(sys.argv[1], 'r') as backup_file:
|
|||
#
|
||||
ynh_restore_file () {
|
||||
# Declare an array to define the options of this helper.
|
||||
declare -Ar args_array=( [o]=origin_path= [d]=dest_path= )
|
||||
declare -Ar args_array=( [o]=origin_path= [d]=dest_path= [m]=not_mandatory )
|
||||
local origin_path
|
||||
local archive_path
|
||||
local dest_path
|
||||
local not_mandatory
|
||||
# Manage arguments with getopts
|
||||
ynh_handle_getopts_args "$@"
|
||||
local origin_path="/${origin_path#/}"
|
||||
local archive_path="$YNH_CWD${origin_path}"
|
||||
# Default value for dest_path = /$origin_path
|
||||
local dest_path="${dest_path:-$origin_path}"
|
||||
local not_mandatory="${not_mandatory:-0}"
|
||||
|
||||
# If archive_path doesn't exist, search for a corresponding path in CSV
|
||||
if [ ! -d "$archive_path" ] && [ ! -f "$archive_path" ] && [ ! -L "$archive_path" ]; then
|
||||
archive_path="$YNH_BACKUP_DIR/$(_get_archive_path \"$origin_path\")"
|
||||
if [ "$not_mandatory" == "0" ]
|
||||
then
|
||||
archive_path="$YNH_BACKUP_DIR/$(_get_archive_path \"$origin_path\")"
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Move the old directory if it already exists
|
||||
|
|
|
@ -129,6 +129,8 @@ ynh_handle_getopts_args () {
|
|||
shift_value=$(( shift_value - 1 ))
|
||||
fi
|
||||
|
||||
# Declare the content of option_var as a variable.
|
||||
eval ${option_var}=""
|
||||
# Then read the array value per value
|
||||
for i in `seq 0 $(( ${#all_args[@]} - 1 ))`
|
||||
do
|
||||
|
@ -140,8 +142,6 @@ ynh_handle_getopts_args () {
|
|||
break
|
||||
fi
|
||||
else
|
||||
# Declare the content of option_var as a variable.
|
||||
eval ${option_var}=""
|
||||
# Else, add this value to this option
|
||||
# Each value will be separated by ';'
|
||||
if [ -n "${!option_var}" ]
|
||||
|
|
|
@ -1,3 +1,26 @@
|
|||
# Check if apt is free to use, or wait, until timeout.
|
||||
#
|
||||
# [internal]
|
||||
#
|
||||
# usage: ynh_wait_dpkg_free
|
||||
ynh_wait_dpkg_free() {
|
||||
local try
|
||||
# With seq 1 17, timeout will be almost 30 minutes
|
||||
for try in `seq 1 17`
|
||||
do
|
||||
# Check if /var/lib/dpkg/lock is used by another process
|
||||
if sudo lsof /var/lib/dpkg/lock > /dev/null
|
||||
then
|
||||
echo "apt is already in use..."
|
||||
# Sleep an exponential time at each round
|
||||
sleep $(( try * try ))
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
echo "apt still used, but timeout reached !"
|
||||
}
|
||||
|
||||
# Check either a package is installed or not
|
||||
#
|
||||
# example: ynh_package_is_installed --package=yunohost && echo "ok"
|
||||
|
@ -11,6 +34,7 @@ ynh_package_is_installed() {
|
|||
# Manage arguments with getopts
|
||||
ynh_handle_getopts_args "$@"
|
||||
|
||||
ynh_wait_dpkg_free
|
||||
dpkg-query -W -f '${Status}' "$package" 2>/dev/null \
|
||||
| grep -c "ok installed" &>/dev/null
|
||||
}
|
||||
|
@ -42,6 +66,7 @@ ynh_package_version() {
|
|||
#
|
||||
# usage: ynh_apt update
|
||||
ynh_apt() {
|
||||
ynh_wait_dpkg_free
|
||||
DEBIAN_FRONTEND=noninteractive sudo apt-get -y $@
|
||||
}
|
||||
|
||||
|
@ -117,6 +142,7 @@ ynh_package_install_from_equivs () {
|
|||
# Create a fake deb package with equivs-build and the given control file
|
||||
# Install the fake package without its dependencies with dpkg
|
||||
# Install missing dependencies with ynh_package_install
|
||||
ynh_wait_dpkg_free
|
||||
(cp "$controlfile" "${TMPDIR}/control" && cd "$TMPDIR" \
|
||||
&& equivs-build ./control 1>/dev/null \
|
||||
&& sudo dpkg --force-depends \
|
||||
|
|
|
@ -14,7 +14,7 @@ ynh_die() {
|
|||
|
||||
# Display a message in the 'INFO' logging category
|
||||
#
|
||||
# usage: ynh_info --message="Some message"
|
||||
# usage: ynh_print_info --message="Some message"
|
||||
ynh_print_info() {
|
||||
# Declare an array to define the options of this helper.
|
||||
declare -Ar args_array=( [m]=message= )
|
||||
|
|
|
@ -12,7 +12,7 @@ ynh_string_random() {
|
|||
ynh_handle_getopts_args "$@"
|
||||
length=${length:-24}
|
||||
|
||||
dd if=/dev/urandom bs=1 count=200 2> /dev/null \
|
||||
dd if=/dev/urandom bs=1 count=1000 2> /dev/null \
|
||||
| tr -c -d 'A-Za-z0-9' \
|
||||
| sed -n 's/\(.\{'"$length"'\}\).*/\1/p'
|
||||
}
|
||||
|
|
|
@ -258,6 +258,9 @@ ynh_local_curl () {
|
|||
# Add --data arg and remove the last character, which is an unecessary '&'
|
||||
POST_data="--data ${POST_data::-1}"
|
||||
fi
|
||||
|
||||
# Wait untils nginx has fully reloaded (avoid curl fail with http2)
|
||||
sleep 2
|
||||
|
||||
# Curl the URL
|
||||
curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url"
|
||||
|
@ -274,6 +277,7 @@ ynh_local_curl () {
|
|||
ynh_render_template() {
|
||||
local template_path=$1
|
||||
local output_path=$2
|
||||
mkdir -p "$(dirname $output_path)"
|
||||
# Taken from https://stackoverflow.com/a/35009576
|
||||
python2.7 -c 'import os, sys, jinja2; sys.stdout.write(
|
||||
jinja2.Template(sys.stdin.read()
|
||||
|
|
|
@ -24,46 +24,57 @@ do_init_regen() {
|
|||
|
||||
# initialize some files
|
||||
[[ -f "${ssl_dir}/serial" ]] \
|
||||
|| echo "00" > "${ssl_dir}/serial"
|
||||
|| openssl rand -hex 19 > "${ssl_dir}/serial"
|
||||
[[ -f "${ssl_dir}/index.txt" ]] \
|
||||
|| touch "${ssl_dir}/index.txt"
|
||||
|
||||
openssl_conf="/usr/share/yunohost/templates/ssl/openssl.cnf"
|
||||
ynh_ca="/etc/yunohost/certs/yunohost.org/ca.pem"
|
||||
ynh_crt="/etc/yunohost/certs/yunohost.org/crt.pem"
|
||||
ynh_key="/etc/yunohost/certs/yunohost.org/key.pem"
|
||||
|
||||
# create default certificates
|
||||
if [[ ! -f /etc/yunohost/certs/yunohost.org/ca.pem ]]; then
|
||||
if [[ ! -f "$ynh_ca" ]]; then
|
||||
echo -e "\n# Creating the CA key (?)\n" >>$LOGFILE
|
||||
openssl req -x509 -new -config "$openssl_conf" \
|
||||
-days 3650 -out "${ssl_dir}/ca/cacert.pem" \
|
||||
-keyout "${ssl_dir}/ca/cakey.pem" -nodes -batch >>$LOGFILE 2>&1
|
||||
cp "${ssl_dir}/ca/cacert.pem" \
|
||||
/etc/yunohost/certs/yunohost.org/ca.pem
|
||||
ln -sf /etc/yunohost/certs/yunohost.org/ca.pem \
|
||||
/etc/ssl/certs/ca-yunohost_crt.pem
|
||||
|
||||
openssl req -x509 \
|
||||
-new \
|
||||
-config "$openssl_conf" \
|
||||
-days 3650 \
|
||||
-out "${ssl_dir}/ca/cacert.pem" \
|
||||
-keyout "${ssl_dir}/ca/cakey.pem" \
|
||||
-nodes -batch >>$LOGFILE 2>&1
|
||||
|
||||
cp "${ssl_dir}/ca/cacert.pem" "$ynh_ca"
|
||||
ln -sf "$ynh_ca" /etc/ssl/certs/ca-yunohost_crt.pem
|
||||
update-ca-certificates
|
||||
fi
|
||||
|
||||
if [[ ! -f /etc/yunohost/certs/yunohost.org/crt.pem ]]; then
|
||||
if [[ ! -f "$ynh_crt" ]]; then
|
||||
echo -e "\n# Creating initial key and certificate (?)\n" >>$LOGFILE
|
||||
openssl req -new -config "$openssl_conf" \
|
||||
-days 730 -out "${ssl_dir}/certs/yunohost_csr.pem" \
|
||||
-keyout "${ssl_dir}/certs/yunohost_key.pem" -nodes -batch >>$LOGFILE 2>&1
|
||||
openssl ca -config "$openssl_conf" \
|
||||
-days 730 -in "${ssl_dir}/certs/yunohost_csr.pem" \
|
||||
-out "${ssl_dir}/certs/yunohost_crt.pem" -batch >>$LOGFILE 2>&1
|
||||
|
||||
openssl req -new \
|
||||
-config "$openssl_conf" \
|
||||
-days 730 \
|
||||
-out "${ssl_dir}/certs/yunohost_csr.pem" \
|
||||
-keyout "${ssl_dir}/certs/yunohost_key.pem" \
|
||||
-nodes -batch >>$LOGFILE 2>&1
|
||||
|
||||
openssl ca \
|
||||
-config "$openssl_conf" \
|
||||
-days 730 \
|
||||
-in "${ssl_dir}/certs/yunohost_csr.pem" \
|
||||
-out "${ssl_dir}/certs/yunohost_crt.pem" \
|
||||
-batch >>$LOGFILE 2>&1
|
||||
|
||||
last_cert=$(ls $ssl_dir/newcerts/*.pem | sort -V | tail -n 1)
|
||||
chmod 640 "${ssl_dir}/certs/yunohost_key.pem"
|
||||
chmod 640 "$last_cert"
|
||||
|
||||
cp "${ssl_dir}/certs/yunohost_key.pem" \
|
||||
/etc/yunohost/certs/yunohost.org/key.pem
|
||||
cp "$last_cert" \
|
||||
/etc/yunohost/certs/yunohost.org/crt.pem
|
||||
ln -sf /etc/yunohost/certs/yunohost.org/crt.pem \
|
||||
/etc/ssl/certs/yunohost_crt.pem
|
||||
ln -sf /etc/yunohost/certs/yunohost.org/key.pem \
|
||||
/etc/ssl/private/yunohost_key.pem
|
||||
cp "${ssl_dir}/certs/yunohost_key.pem" "$ynh_key"
|
||||
cp "$last_cert" "$ynh_crt"
|
||||
ln -sf "$ynh_crt" /etc/ssl/certs/yunohost_crt.pem
|
||||
ln -sf "$ynh_key" /etc/ssl/private/yunohost_key.pem
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
@ -2,28 +2,53 @@
|
|||
|
||||
set -e
|
||||
|
||||
. /usr/share/yunohost/helpers.d/utils
|
||||
|
||||
do_pre_regen() {
|
||||
pending_dir=$1
|
||||
pending_dir=$1
|
||||
|
||||
cd /usr/share/yunohost/templates/ssh
|
||||
# If the (legacy) 'from_script' flag is here,
|
||||
# we won't touch anything in the ssh config.
|
||||
[[ ! -f /etc/yunohost/from_script ]] || return 0
|
||||
|
||||
# only overwrite SSH configuration on an ISO installation
|
||||
if [[ ! -f /etc/yunohost/from_script ]]; then
|
||||
# do not listen to IPv6 if unavailable
|
||||
[[ -f /proc/net/if_inet6 ]] \
|
||||
|| sed -i "s/ListenAddress ::/#ListenAddress ::/g" sshd_config
|
||||
cd /usr/share/yunohost/templates/ssh
|
||||
|
||||
# do not listen to IPv6 if unavailable
|
||||
[[ -f /proc/net/if_inet6 ]] && ipv6_enabled=true || ipv6_enabled=false
|
||||
|
||||
install -D -m 644 sshd_config "${pending_dir}/etc/ssh/sshd_config"
|
||||
fi
|
||||
# Support legacy setting (this setting might be disabled by a user during a migration)
|
||||
ssh_keys=$(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key 2>/dev/null)
|
||||
if [[ "$(yunohost settings get 'service.ssh.allow_deprecated_dsa_hostkey')" == "True" ]]; then
|
||||
ssh_keys="$ssh_keys $(ls /etc/ssh/ssh_host_dsa_key 2>/dev/null)"
|
||||
fi
|
||||
|
||||
ssh_keys=$(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key 2>/dev/null)
|
||||
|
||||
# Support legacy setting (this setting might be disabled by a user during a migration)
|
||||
if [[ "$(yunohost settings get 'service.ssh.allow_deprecated_dsa_hostkey')" == "True" ]]; then
|
||||
ssh_keys="$ssh_keys $(ls /etc/ssh/ssh_host_dsa_key 2>/dev/null)"
|
||||
fi
|
||||
|
||||
export ssh_keys
|
||||
export ipv6_enabled
|
||||
ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config"
|
||||
}
|
||||
|
||||
do_post_regen() {
|
||||
regen_conf_files=$1
|
||||
regen_conf_files=$1
|
||||
|
||||
if [[ ! -f /etc/yunohost/from_script ]]; then
|
||||
[[ -z "$regen_conf_files" ]] \
|
||||
|| sudo service ssh restart
|
||||
fi
|
||||
# If the (legacy) 'from_script' flag is here,
|
||||
# we won't touch anything in the ssh config.
|
||||
[[ ! -f /etc/yunohost/from_script ]] || return 0
|
||||
|
||||
# If no file changed, there's nothing to do
|
||||
[[ -n "$regen_conf_files" ]] || return 0
|
||||
|
||||
# Enforce permissions for /etc/ssh/sshd_config
|
||||
chown root:root "/etc/ssh/sshd_config"
|
||||
chmod 644 "/etc/ssh/sshd_config"
|
||||
|
||||
systemctl restart ssh
|
||||
}
|
||||
|
||||
FORCE=${2:-0}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
set -e
|
||||
|
||||
. /usr/share/yunohost/helpers.d/utils
|
||||
|
||||
do_init_regen() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "You must be root to run this script" 1>&2
|
||||
|
@ -42,18 +44,18 @@ do_pre_regen() {
|
|||
mkdir -p "$mail_autoconfig_dir"
|
||||
|
||||
# NGINX server configuration
|
||||
cat server.tpl.conf \
|
||||
| sed "s/{{ domain }}/${domain}/g" \
|
||||
> "${nginx_conf_dir}/${domain}.conf"
|
||||
|
||||
cat autoconfig.tpl.xml \
|
||||
| sed "s/{{ domain }}/${domain}/g" \
|
||||
> "${mail_autoconfig_dir}/config-v1.1.xml"
|
||||
export domain
|
||||
export domain_cert_ca=$(yunohost domain cert-status $domain --json \
|
||||
| jq ".certificates.\"$domain\".CA_type" \
|
||||
| tr -d '"')
|
||||
|
||||
ynh_render_template "server.tpl.conf" "${nginx_conf_dir}/${domain}.conf"
|
||||
ynh_render_template "autoconfig.tpl.xml" "${mail_autoconfig_dir}/config-v1.1.xml"
|
||||
|
||||
[[ $main_domain != $domain ]] \
|
||||
&& touch "${domain_conf_dir}/yunohost_local.conf" \
|
||||
|| cp yunohost_local.conf "${domain_conf_dir}/yunohost_local.conf"
|
||||
|
||||
done
|
||||
|
||||
# remove old domain conf files
|
||||
|
|
BIN
data/other/password/100000-most-used.txt.gz
Normal file
BIN
data/other/password/100000-most-used.txt.gz
Normal file
Binary file not shown.
|
@ -9,16 +9,64 @@
|
|||
-- A table is a list of values, except each value has a name. An
|
||||
-- example would be:
|
||||
--
|
||||
-- ssl = { key = "keyfile.key", certificate = "certificate.crt" }
|
||||
-- ssl = { key = "keyfile.key", certificate = "certificate.cert" }
|
||||
--
|
||||
-- Tip: You can check that the syntax of this file is correct when you have finished
|
||||
-- by running: luac -p metronome.cfg.lua
|
||||
-- If there are any errors, it will let you know what and where they are, otherwise it
|
||||
-- will keep quiet.
|
||||
|
||||
---------- Server-wide settings ----------
|
||||
-- Settings in this section apply to the whole server and are the default settings
|
||||
-- for any virtual hosts
|
||||
-- Global settings go in this section
|
||||
|
||||
-- This is the list of modules Metronome will load on startup.
|
||||
-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
|
||||
|
||||
modules_enabled = {
|
||||
-- Generally required
|
||||
"roster"; -- Allow users to have a roster. Recommended.
|
||||
"saslauth"; -- Authentication for clients. Recommended if you want to log in.
|
||||
"tls"; -- Add support for secure TLS on c2s/s2s connections
|
||||
"disco"; -- Service discovery
|
||||
|
||||
-- Not essential, but recommended
|
||||
"private"; -- Private XML storage (for room bookmarks, etc.)
|
||||
"vcard"; -- Allow users to set vCards
|
||||
"pep"; -- Allows setting of mood, tune, etc.
|
||||
"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
|
||||
"bidi"; -- Enables Bidirectional Server-to-Server Streams.
|
||||
|
||||
-- Nice to have
|
||||
"version"; -- Replies to server version requests
|
||||
"uptime"; -- Report how long server has been running
|
||||
"time"; -- Let others know the time here on this server
|
||||
"ping"; -- Replies to XMPP pings with pongs
|
||||
"register"; -- Allow users to register on this server using a client and change passwords
|
||||
"stream_management"; -- Allows clients and servers to use Stream Management
|
||||
"stanza_optimizations"; -- Allows clients to use Client State Indication and SIFT
|
||||
"message_carbons"; -- Allows clients to enable carbon copies of messages
|
||||
"mam"; -- Enable server-side message archives using Message Archive Management
|
||||
"push"; -- Enable Push Notifications via PubSub using XEP-0357
|
||||
"lastactivity"; -- Enables clients to know the last presence status of an user
|
||||
"adhoc_cm"; -- Allow to set client certificates to login through SASL External via adhoc
|
||||
"admin_adhoc"; -- administration adhoc commands
|
||||
"bookmarks"; -- XEP-0048 Bookmarks synchronization between PEP and Private Storage
|
||||
"sec_labels"; -- Allows to use a simplified version XEP-0258 Security Labels and related ACDFs.
|
||||
"privacy"; -- Add privacy lists and simple blocking command support
|
||||
|
||||
-- Other specific functionality
|
||||
--"admin_telnet"; -- administration console, telnet to port 5582
|
||||
--"admin_web"; -- administration web interface
|
||||
"bosh"; -- Enable support for BOSH clients, aka "XMPP over Bidirectional Streams over Synchronous HTTP"
|
||||
--"compression"; -- Allow clients to enable Stream Compression
|
||||
--"spim_block"; -- Require authorization via OOB form for messages from non-contacts and block unsollicited messages
|
||||
--"gate_guard"; -- Enable config-based blacklisting and hit-based auto-banning features
|
||||
--"incidents_handling"; -- Enable Incidents Handling support (can be administered via adhoc commands)
|
||||
--"server_presence"; -- Enables Server Buddies extension support
|
||||
--"service_directory"; -- Enables Service Directories extension support
|
||||
--"public_service"; -- Enables Server vCard support for public services in directories and advertises in features
|
||||
--"register_api"; -- Provides secure API for both Out-Of-Band and In-Band registration for E-Mail verification
|
||||
"websocket"; -- Enable support for WebSocket clients, aka "XMPP over WebSockets"
|
||||
};
|
||||
|
||||
-- Server PID
|
||||
pidfile = "/var/run/metronome/metronome.pid"
|
||||
|
@ -33,156 +81,76 @@ http_interfaces = { "127.0.0.1", "::1" }
|
|||
-- Enable IPv6
|
||||
use_ipv6 = true
|
||||
|
||||
-- This is the list of modules Metronome will load on startup.
|
||||
-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
|
||||
modules_enabled = {
|
||||
|
||||
-- Generally required
|
||||
"roster"; -- Allow users to have a roster. Recommended ;)
|
||||
"saslauth"; -- Authentication for clients and servers. Recommended if you want to log in.
|
||||
"tls"; -- Add support for secure TLS on c2s/s2s connections
|
||||
"dialback"; -- s2s dialback support
|
||||
"disco"; -- Service discovery
|
||||
--"discoitems"; -- Service discovery items
|
||||
--"extdisco"; -- External Service Discovery
|
||||
|
||||
-- Not essential, but recommended
|
||||
"private"; -- Private XML storage (for room bookmarks, etc.)
|
||||
"vcard"; -- Allow users to set vCards
|
||||
"privacy"; -- Support privacy lists
|
||||
|
||||
-- These are commented by default as they have a performance impact
|
||||
--"compression"; -- Stream compression (Debian: requires lua-zlib module to work)
|
||||
|
||||
-- Nice to have
|
||||
"version"; -- Replies to server version requests
|
||||
"uptime"; -- Report how long server has been running
|
||||
"time"; -- Let others know the time here on this server
|
||||
"ping"; -- Replies to XMPP pings with pongs
|
||||
"pep"; -- Enables users to publish their mood, activity, playing music and more
|
||||
"message_carbons"; -- Allow clients to keep in sync with messages send on other resources
|
||||
"register"; -- Allow users to register on this server using a client and change passwords
|
||||
"adhoc"; -- Support for "ad-hoc commands" that can be executed with an XMPP client
|
||||
|
||||
-- Admin interfaces
|
||||
"admin_adhoc"; -- Allows administration via an XMPP client that supports ad-hoc commands
|
||||
"admin_telnet"; -- Opens telnet console interface on localhost port 5582
|
||||
|
||||
-- HTTP modules
|
||||
"bosh"; -- Enable BOSH clients, aka "Jabber over HTTP"
|
||||
--"websockets"; -- Enable WebSocket clients
|
||||
--"http_files"; -- Serve static files from a directory over HTTP
|
||||
|
||||
-- Other specific functionality
|
||||
-- "bidi"; -- Bidirectional Streams for S2S connections
|
||||
-- "stream_management"; -- Stream Management support
|
||||
--"groups"; -- Shared roster support
|
||||
--"announce"; -- Send announcement to all online users
|
||||
--"welcome"; -- Welcome users who register accounts
|
||||
--"watchregistrations"; -- Alert admins of registrations
|
||||
--"motd"; -- Send a message to users when they log in
|
||||
"mam"; -- Nice archive management
|
||||
--"legacyauth"; -- Legacy authentication. Only used by some old clients and bots.
|
||||
"offline"; -- Store offline messages
|
||||
"c2s"; -- Handle client connections
|
||||
"s2s"; -- Handle server-to-server connections
|
||||
|
||||
-- Debian: do not remove this module, or you lose syslog
|
||||
-- support
|
||||
"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
|
||||
};
|
||||
|
||||
-- Discovery items
|
||||
disco_items = {
|
||||
{ "muc.{{ main_domain }}" },
|
||||
{ "pubsub.{{ main_domain }}" },
|
||||
{ "vjud.{{ main_domain }}" }
|
||||
{ "muc.{{ main_domain }}" },
|
||||
{ "pubsub.{{ main_domain }}" },
|
||||
{ "upload.{{ main_domain }}" },
|
||||
{ "vjud.{{ main_domain }}" }
|
||||
};
|
||||
|
||||
-- BOSH configuration (mod_bosh)
|
||||
bosh_max_inactivity = 30
|
||||
consider_bosh_secure = true
|
||||
cross_domain_bosh = true
|
||||
|
||||
-- WebSocket configuration (mod_websocket)
|
||||
consider_websocket_secure = true
|
||||
cross_domain_websocket = true
|
||||
|
||||
-- Disable account creation by default, for security
|
||||
allow_registration = false
|
||||
|
||||
-- SSL/TLS configuration
|
||||
ssl = {
|
||||
options = {
|
||||
"no_sslv2",
|
||||
"no_sslv3",
|
||||
"no_ticket",
|
||||
"no_compression",
|
||||
"cipher_server_preference"
|
||||
};
|
||||
}
|
||||
|
||||
-- Force clients to use encrypted connections? This option will
|
||||
-- prevent clients from authenticating unless they are using encryption.
|
||||
c2s_require_encryption = true
|
||||
|
||||
-- Force servers to use encrypted connections? This option will
|
||||
-- prevent servers from connecting unless they are using encryption.
|
||||
s2s_require_encryption = true
|
||||
|
||||
-- Allow servers to use an unauthenticated encryption channel
|
||||
s2s_allow_encryption = true
|
||||
|
||||
allow_unencrypted_plain_auth = false;
|
||||
|
||||
s2s_secure = true
|
||||
s2s_secure_auth = false
|
||||
|
||||
--anonymous_login = false
|
||||
|
||||
-- Use LDAP storage backend for all stores
|
||||
storage = "ldap"
|
||||
|
||||
-- Logging configuration
|
||||
log = {
|
||||
info = "/var/log/metronome/metronome.log"; -- Change 'info' to 'debug' for verbose logging
|
||||
error = "/var/log/metronome/metronome.err";
|
||||
-- "*syslog"; -- Uncomment this for logging to syslog
|
||||
-- "*console"; -- Log to the console, useful for debugging with daemonize=false
|
||||
info = "/var/log/metronome/metronome.log"; -- Change 'info' to 'debug' for verbose logging
|
||||
error = "/var/log/metronome/metronome.err";
|
||||
-- "*syslog"; -- Uncomment this for logging to syslog
|
||||
-- "*console"; -- Log to the console, useful for debugging with daemonize=false
|
||||
}
|
||||
|
||||
|
||||
------ Components ------
|
||||
-- You can specify components to add hosts that provide special services,
|
||||
-- like multi-user conferences, and transports.
|
||||
|
||||
---Set up a local BOSH service
|
||||
Component "localhost" "http"
|
||||
modules_enabled = { "bosh" }
|
||||
modules_enabled = { "bosh" }
|
||||
|
||||
---Set up a MUC (multi-user chat) room server
|
||||
Component "muc.{{ main_domain }}" "muc"
|
||||
name = "{{ main_domain }} Chatrooms"
|
||||
name = "{{ main_domain }} Chatrooms"
|
||||
|
||||
modules_enabled = {
|
||||
"muc_limits";
|
||||
"muc_log";
|
||||
"muc_log_http";
|
||||
modules_enabled = {
|
||||
"muc_limits";
|
||||
"muc_log";
|
||||
"muc_log_mam";
|
||||
"muc_log_http";
|
||||
"muc_vcard";
|
||||
}
|
||||
|
||||
muc_event_rate = 0.5
|
||||
muc_burst_factor = 10
|
||||
|
||||
muc_log_http_config = {
|
||||
url_base = "logs";
|
||||
theme = "metronome";
|
||||
}
|
||||
muc_event_rate = 0.5
|
||||
muc_burst_factor = 10
|
||||
|
||||
---Set up a PubSub server
|
||||
Component "pubsub.{{ main_domain }}" "pubsub"
|
||||
name = "{{ main_domain }} Publish/Subscribe"
|
||||
name = "{{ main_domain }} Publish/Subscribe"
|
||||
|
||||
unrestricted_node_creation = true -- Anyone can create a PubSub node (from any server)
|
||||
|
||||
---Set up a HTTP Upload service
|
||||
Component "upload.{{ main_domain }}" "http_upload"
|
||||
name = "{{ main_domain }} Sharing Service"
|
||||
|
||||
http_file_size_limit = 6*1024*1024
|
||||
http_file_quota = 60*1024*1024
|
||||
|
||||
unrestricted_node_creation = true -- Anyone can create a PubSub node (from any server)
|
||||
|
||||
---Set up a VJUD service
|
||||
Component "vjud.{{ main_domain }}" "vjud"
|
||||
ud_disco_name = "{{ main_domain }} User Directory"
|
||||
ud_disco_name = "{{ main_domain }} User Directory"
|
||||
|
||||
|
||||
----------- Virtual hosts -----------
|
||||
|
@ -190,4 +158,3 @@ Component "vjud.{{ main_domain }}" "vjud"
|
|||
-- Settings under each VirtualHost entry apply *only* to that host.
|
||||
|
||||
Include "conf.d/*.cfg.lua"
|
||||
|
||||
|
|
|
@ -73,6 +73,6 @@ server {
|
|||
access_by_lua_file /usr/share/ssowat/access.lua;
|
||||
}
|
||||
|
||||
include conf.d/yunohost_admin.conf.inc;
|
||||
include conf.d/yunohost_api.conf.inc;
|
||||
include /etc/nginx/conf.d/yunohost_admin.conf.inc;
|
||||
include /etc/nginx/conf.d/yunohost_api.conf.inc;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ location /yunohost/api/ {
|
|||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $http_host;
|
||||
|
||||
# Custom 502 error page
|
||||
error_page 502 /yunohost/api/error/502;
|
||||
|
|
|
@ -5,13 +5,13 @@ server {
|
|||
|
||||
access_by_lua_file /usr/share/ssowat/access.lua;
|
||||
|
||||
include conf.d/{{ domain }}.d/*.conf;
|
||||
include /etc/nginx/conf.d/{{ domain }}.d/*.conf;
|
||||
|
||||
location /yunohost/admin {
|
||||
return 301 https://$http_host$request_uri;
|
||||
}
|
||||
|
||||
location /.well-known/autoconfig/mail {
|
||||
location /.well-known/autoconfig/mail/ {
|
||||
alias /var/www/.well-known/{{ domain }}/autoconfig/mail;
|
||||
}
|
||||
|
||||
|
@ -20,11 +20,8 @@ server {
|
|||
}
|
||||
|
||||
server {
|
||||
# Disabling http2 for now as it's causing weird issues with curl
|
||||
#listen 443 ssl http2;
|
||||
#listen [::]:443 ssl http2;
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name {{ domain }};
|
||||
|
||||
ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem;
|
||||
|
@ -33,12 +30,7 @@ server {
|
|||
ssl_session_cache shared:SSL:50m;
|
||||
|
||||
# As suggested by Mozilla : https://wiki.mozilla.org/Security/Server_Side_TLS and https://en.wikipedia.org/wiki/Curve25519
|
||||
# (this doesn't work on jessie though ...?)
|
||||
# ssl_ecdh_curve secp521r1:secp384r1:prime256v1;
|
||||
|
||||
# As suggested by https://cipherli.st/
|
||||
ssl_ecdh_curve secp384r1;
|
||||
|
||||
ssl_ecdh_curve secp521r1:secp384r1:prime256v1;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
# Ciphers with intermediate compatibility
|
||||
|
@ -59,21 +51,30 @@ server {
|
|||
# Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners
|
||||
# https://wiki.mozilla.org/Security/Guidelines/Web_Security
|
||||
# https://observatory.mozilla.org/
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
|
||||
add_header Content-Security-Policy "upgrade-insecure-requests";
|
||||
add_header Content-Security-Policy-Report-Only "default-src https: data: 'unsafe-inline' 'unsafe-eval'";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Download-Options noopen;
|
||||
add_header X-Permitted-Cross-Domain-Policies none;
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload";
|
||||
more_set_headers "Content-Security-Policy : upgrade-insecure-requests";
|
||||
more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' 'unsafe-eval'";
|
||||
more_set_headers "X-Content-Type-Options : nosniff";
|
||||
more_set_headers "X-XSS-Protection : 1; mode=block";
|
||||
more_set_headers "X-Download-Options : noopen";
|
||||
more_set_headers "X-Permitted-Cross-Domain-Policies : none";
|
||||
more_set_headers "X-Frame-Options : SAMEORIGIN";
|
||||
|
||||
{% if domain_cert_ca == "Let's Encrypt" %}
|
||||
# OCSP settings
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
ssl_trusted_certificate /etc/yunohost/certs/{{ domain }}/crt.pem;
|
||||
resolver 127.0.0.1 127.0.1.1 valid=300s;
|
||||
resolver_timeout 5s;
|
||||
{% endif %}
|
||||
|
||||
access_by_lua_file /usr/share/ssowat/access.lua;
|
||||
|
||||
include conf.d/{{ domain }}.d/*.conf;
|
||||
include /etc/nginx/conf.d/{{ domain }}.d/*.conf;
|
||||
|
||||
include conf.d/yunohost_admin.conf.inc;
|
||||
include conf.d/yunohost_api.conf.inc;
|
||||
include /etc/nginx/conf.d/yunohost_admin.conf.inc;
|
||||
include /etc/nginx/conf.d/yunohost_api.conf.inc;
|
||||
|
||||
access_log /var/log/nginx/{{ domain }}-access.log;
|
||||
error_log /var/log/nginx/{{ domain }}-error.log;
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
# See ldap.conf(5) for details
|
||||
# This file should be world readable but not world writable.
|
||||
|
||||
#BASE dc=example,dc=com
|
||||
#URI ldap://ldap.example.com ldap://ldap-master.example.com:666
|
||||
BASE dc=yunohost,dc=org
|
||||
URI ldap://localhost:389
|
||||
|
||||
#SIZELIMIT 12
|
||||
#TIMELIMIT 15
|
||||
|
|
|
@ -1,96 +1,78 @@
|
|||
# Package generated configuration file
|
||||
# See the sshd_config(5) manpage for details
|
||||
# This configuration has been automatically generated
|
||||
# by YunoHost
|
||||
|
||||
# What ports, IPs and protocols we listen for
|
||||
Port 22
|
||||
# Use these options to restrict which interfaces/protocols sshd will bind to
|
||||
ListenAddress ::
|
||||
ListenAddress 0.0.0.0
|
||||
Protocol 2
|
||||
# HostKeys for protocol version 2
|
||||
HostKey /etc/ssh/ssh_host_rsa_key
|
||||
HostKey /etc/ssh/ssh_host_dsa_key
|
||||
#Privilege Separation is turned on for security
|
||||
UsePrivilegeSeparation yes
|
||||
Port 22
|
||||
|
||||
# Lifetime and size of ephemeral version 1 server key
|
||||
KeyRegenerationInterval 3600
|
||||
ServerKeyBits 768
|
||||
{% if ipv6_enabled == "true" %}ListenAddress ::{% endif %}
|
||||
ListenAddress 0.0.0.0
|
||||
|
||||
# Logging
|
||||
{% for key in ssh_keys.split() %}
|
||||
HostKey {{ key }}{% endfor %}
|
||||
|
||||
# ##############################################
|
||||
# Stuff recommended by Mozilla "modern" compat'
|
||||
# https://infosec.mozilla.org/guidelines/openssh
|
||||
# ##############################################
|
||||
|
||||
# Keys, ciphers and MACS
|
||||
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256
|
||||
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
|
||||
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com
|
||||
|
||||
# Use kernel sandbox mechanisms where possible in unprivileged processes
|
||||
UsePrivilegeSeparation sandbox
|
||||
|
||||
# LogLevel VERBOSE logs user's key fingerprint on login.
|
||||
# Needed to have a clear audit track of which key was using to log in.
|
||||
SyslogFacility AUTH
|
||||
LogLevel INFO
|
||||
LogLevel VERBOSE
|
||||
|
||||
# #######################
|
||||
# Authentication settings
|
||||
# #######################
|
||||
|
||||
# Comment from Mozilla about the motivation behind disabling root login
|
||||
#
|
||||
# Root login is not allowed for auditing reasons. This is because it's difficult to track which process belongs to which root user:
|
||||
#
|
||||
# On Linux, user sessions are tracking using a kernel-side session id, however, this session id is not recorded by OpenSSH.
|
||||
# Additionally, only tools such as systemd and auditd record the process session id.
|
||||
# On other OSes, the user session id is not necessarily recorded at all kernel-side.
|
||||
# Using regular users in combination with /bin/su or /usr/bin/sudo ensure a clear audit track.
|
||||
|
||||
# Authentication:
|
||||
LoginGraceTime 120
|
||||
PermitRootLogin no
|
||||
StrictModes yes
|
||||
|
||||
RSAAuthentication yes
|
||||
PubkeyAuthentication yes
|
||||
#AuthorizedKeysFile %h/.ssh/authorized_keys
|
||||
|
||||
# Don't read the user's ~/.rhosts and ~/.shosts files
|
||||
IgnoreRhosts yes
|
||||
# For this to work you will also need host keys in /etc/ssh_known_hosts
|
||||
RhostsRSAAuthentication no
|
||||
# similar for protocol version 2
|
||||
HostbasedAuthentication no
|
||||
# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
|
||||
#IgnoreUserKnownHosts yes
|
||||
|
||||
# To enable empty passwords, change to yes (NOT RECOMMENDED)
|
||||
PermitEmptyPasswords no
|
||||
|
||||
# Change to yes to enable challenge-response passwords (beware issues with
|
||||
# some PAM modules and threads)
|
||||
ChallengeResponseAuthentication no
|
||||
|
||||
# Change to no to disable tunnelled clear text passwords
|
||||
#PasswordAuthentication yes
|
||||
|
||||
# Kerberos options
|
||||
#KerberosAuthentication no
|
||||
#KerberosGetAFSToken no
|
||||
#KerberosOrLocalPasswd yes
|
||||
#KerberosTicketCleanup yes
|
||||
|
||||
# GSSAPI options
|
||||
#GSSAPIAuthentication no
|
||||
#GSSAPICleanupCredentials yes
|
||||
|
||||
X11Forwarding yes
|
||||
X11DisplayOffset 10
|
||||
PrintMotd no
|
||||
PrintLastLog yes
|
||||
TCPKeepAlive yes
|
||||
#UseLogin no
|
||||
|
||||
# keep ssh sessions fresh
|
||||
ClientAliveInterval 60
|
||||
|
||||
#MaxStartups 10:30:60
|
||||
Banner /etc/issue.net
|
||||
|
||||
# Allow client to pass locale environment variables
|
||||
AcceptEnv LANG LC_*
|
||||
|
||||
Subsystem sftp internal-sftp
|
||||
|
||||
# Set this to 'yes' to enable PAM authentication, account processing,
|
||||
# and session processing. If this is enabled, PAM authentication will
|
||||
# be allowed through the ChallengeResponseAuthentication and
|
||||
# PasswordAuthentication. Depending on your PAM configuration,
|
||||
# PAM authentication via ChallengeResponseAuthentication may bypass
|
||||
# the setting of "PermitRootLogin without-password".
|
||||
# If you just want the PAM account and session checks to run without
|
||||
# PAM authentication, then enable this but set PasswordAuthentication
|
||||
# and ChallengeResponseAuthentication to 'no'.
|
||||
UsePAM yes
|
||||
|
||||
# Change to no to disable tunnelled clear text passwords
|
||||
# (i.e. everybody will need to authenticate using ssh keys)
|
||||
#PasswordAuthentication yes
|
||||
|
||||
# Post-login stuff
|
||||
Banner /etc/issue.net
|
||||
PrintMotd no
|
||||
PrintLastLog yes
|
||||
ClientAliveInterval 60
|
||||
AcceptEnv LANG LC_*
|
||||
|
||||
# SFTP stuff
|
||||
Subsystem sftp internal-sftp
|
||||
Match User sftpusers
|
||||
ForceCommand internal-sftp
|
||||
ChrootDirectory /home/%u
|
||||
AllowTcpForwarding no
|
||||
GatewayPorts no
|
||||
X11Forwarding no
|
||||
|
||||
# root login is allowed on local networks
|
||||
# It's meant to be a backup solution in case LDAP is down and
|
||||
# user admin can't be used...
|
||||
# If the server is a VPS, it's expected that the owner of the
|
||||
# server has access to a web console through which to log in.
|
||||
Match Address 192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,169.254.0.0/16,fe80::/10,fd00::/8
|
||||
PermitRootLogin yes
|
||||
|
|
90
debian/changelog
vendored
90
debian/changelog
vendored
|
@ -1,3 +1,93 @@
|
|||
yunohost (3.4.0) testing; urgency=low
|
||||
|
||||
* Misc fixes (#601, #600, #593)
|
||||
* [fix] DEBUG-level messages not appearing in actions performed via the API (#603)
|
||||
* [enh] Also remove /var/mail/<username> directory on user delete (with --purge option) (#602)
|
||||
* [enh] Ask confirmation before installing low-quality, experimental or third party apps (#598)
|
||||
* [fix] Repair tests (#595)
|
||||
* [enh] Clean + harden sshd config using Mozilla recommendation (#590
|
||||
* [fix] Add libpam-ldapd as dependency to be able to login through SSH with LDAP? (#587)
|
||||
* [enh] Add post_cert_update hook each time certificate is updated (#586)
|
||||
* [enh] Enable HTTP2 (#580)
|
||||
* [enh] Update ECDH curves recommended by Mozilla, now that we are on stretch (#579)
|
||||
* [enh] Allow to not fail on backup and restore for non-mandatory files (#576)
|
||||
* [enh] Simplify error management (#574)
|
||||
* [enh] Use more_set_headers in nginx config + fixes for path traversal issues (#564)
|
||||
* [enh] Display human readable date and clarify timezone handling (#552)
|
||||
* [fix] Do not use separate ini file for php pools anymore (#548)
|
||||
* [enh] Improve UPnP support (#542)
|
||||
* [fix] Standardize sshd configuration (#518)
|
||||
* [fix] DKIM keys for new domains werent generated (0445aed)
|
||||
* [i18n] Improve translations for Arabic, Italian and Spanish
|
||||
|
||||
Thanks to all contributors (Aleks, A. Pierré, ButterflyOfFire, Bram, irina11y, Josué, Maniack Crudelis, Sylkevicious, T. Hill, chateau, frju365, gdayon, liberodark, ljf, nqb, wilPoly) ! <3
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 20 Dec 2018 22:13:00 +0000
|
||||
|
||||
yunohost (3.3.3) stable; urgency=low
|
||||
|
||||
* [fix] ynh_wait_dpkg_free displaying a warning despite everything being okay (#593)
|
||||
* [fix] Quotes for recommended CAA DNS record (#596)
|
||||
* [fix] Manual migration and disclaimer behaviors (#594)
|
||||
* [fix] Explicit root password change each time admin password is changed
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Sun, 09 Dec 2018 20:58:00 +0000
|
||||
|
||||
yunohost (3.3.2) stable; urgency=low
|
||||
|
||||
* [fix] Regen nginx conf to be sure it integrates OCSP Stapling (#588)
|
||||
* [fix] Broken new settings and options to control passwords checks / constrains (#589)
|
||||
* [fix] Log dyndns update only if we really update something (#591)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Sun, 02 Dec 2018 17:23:00 +0000
|
||||
|
||||
yunohost (3.3.1) stable; urgency=low
|
||||
|
||||
* [fix] Wait for dpkg lock to be free in apt helpers (#571)
|
||||
* [fix] app_removeaccess call set.add (#573)
|
||||
* [fix] Fix app_addaccess behaviour when 'allowed_users' is initially empty (#575)
|
||||
* [fix] Typo in user_update when update password (#577)
|
||||
* [fix] Do not fail on missing fail2ban config during the backup (#558)
|
||||
* [fix] Generate a random serial for local certification auth (followup of #557)
|
||||
* [i18n] Update Italian, Occitan, French translations
|
||||
|
||||
Thanks to all contributors (Maniack, airwoodix, Aleks, ljf, silkevicious, Quent-in, Jibec) <3 !
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Fri, 23 Nov 2018 15:58:00 +0000
|
||||
|
||||
yunohost (3.3.0) testing; urgency=low
|
||||
|
||||
Highlights
|
||||
==========
|
||||
|
||||
* [enh] Synchronize root password with admin password (#527)
|
||||
* [enh] Check for weak passwords whenever a password is defined (#196)
|
||||
* [fix] 'dyndns update' now checks the upstream DNS record (#519)
|
||||
* [fix] Update Metronome configuration file to v3.11 standard (#559)
|
||||
* [fix] Some php conf files wre not properly removed when an app was uninstalled (#566)
|
||||
* [i18n] Improve Catalan, French, Occitan, Portuguese, Arabic, Italian translations
|
||||
|
||||
Misc
|
||||
====
|
||||
|
||||
* [enh] Add OCSP Stapling to nginx configuration if using Lets Encrypt (#533)
|
||||
* [enh] Add CAA record in recommended DNS conf (#528)
|
||||
* [helpers] Add `ynh_delete_file_checksum` (#524)
|
||||
* [helpers] When using `ynh_setup_source`, silent unecessary messages (#545)
|
||||
* [helpers] Use more blocks for dd in `ynh_string_random` (#569)
|
||||
* [fix] Potential key error when retrieving install_time (#551)
|
||||
* [fix] Allow `-` in user last names (#565)
|
||||
* [fix] Fix possible HTTP2 issue with curl (#547)
|
||||
* [fix] Fix BASE/URI in ldap conf (#554)
|
||||
* [fix] Use random serial number for CA (prevent browser from complaining about some selfsigned certs) (#557)
|
||||
* [enh] Pass Host header to YunoHost API (#560)
|
||||
* [enh] Sort backup list according to their date (#562)
|
||||
* [fix] Improve UX when admin tries to allocate reserved email alias (#553)
|
||||
|
||||
Thanks to all contributors (ljf, irinia11y, Maniack, xaloc33, Bram, flashemade, Maranda, Josue, frju365, Aleks, randomstuff, jershon, Genma, Quent-in, MyNameIsTroll, ButterflyOfFire, Jibec, silkevicious) ! <3
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 08 Nov 2018 17:09:00 +0000
|
||||
|
||||
yunohost (3.2.2) stable; urgency=low
|
||||
|
||||
* [hotfix] mod_auth_ldap: reflect SASL API changes in latest Metronome (#546)
|
||||
|
|
4
debian/control
vendored
4
debian/control
vendored
|
@ -17,7 +17,7 @@ Depends: ${python:Depends}, ${misc:Depends}
|
|||
, dnsutils, bind9utils, unzip, git, curl, cron, wget, jq
|
||||
, ca-certificates, netcat-openbsd, iproute
|
||||
, mariadb-server, php-mysql | php-mysqlnd
|
||||
, slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd
|
||||
, 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
|
||||
|
@ -26,7 +26,7 @@ Depends: ${python:Depends}, ${misc:Depends}
|
|||
, metronome
|
||||
, rspamd (>= 1.6.0), redis-server, opendkim-tools
|
||||
, haveged, fake-hwclock
|
||||
, equivs
|
||||
, equivs, lsof
|
||||
Recommends: yunohost-admin
|
||||
, openssh-server, ntp, inetutils-ping | iputils-ping
|
||||
, bash-completion, rsyslog, etckeeper
|
||||
|
|
1
debian/install
vendored
1
debian/install
vendored
|
@ -4,6 +4,7 @@ data/bash-completion.d/yunohost /etc/bash_completion.d/
|
|||
data/actionsmap/* /usr/share/moulinette/actionsmap/
|
||||
data/hooks/* /usr/share/yunohost/hooks/
|
||||
data/other/yunoprompt.service /etc/systemd/system/
|
||||
data/other/password/* /usr/share/yunohost/other/password/
|
||||
data/other/* /usr/share/yunohost/yunohost-config/moulinette/
|
||||
data/templates/* /usr/share/yunohost/templates/
|
||||
data/helpers /usr/share/yunohost/
|
||||
|
|
|
@ -11,7 +11,7 @@ def render(data):
|
|||
from ansi2html.style import get_styles
|
||||
|
||||
conv = Ansi2HTMLConverter()
|
||||
shell_css = "\n".join(map(str, get_styles(conv.dark_bg, conv.scheme)))
|
||||
shell_css = "\n".join(map(str, get_styles(conv.dark_bg)))
|
||||
|
||||
def shell_to_html(shell):
|
||||
return conv.convert(shell, False)
|
||||
|
@ -28,6 +28,7 @@ class Parser():
|
|||
|
||||
def __init__(self, filename):
|
||||
|
||||
self.filename = filename
|
||||
self.file = open(filename, "r").readlines()
|
||||
self.blocks = None
|
||||
|
||||
|
@ -42,6 +43,9 @@ class Parser():
|
|||
"code": [] }
|
||||
|
||||
for i, line in enumerate(self.file):
|
||||
|
||||
if line.startswith("#!/bin/bash"):
|
||||
continue
|
||||
|
||||
line = line.rstrip().replace("\t", " ")
|
||||
|
||||
|
@ -64,7 +68,7 @@ class Parser():
|
|||
else:
|
||||
# We're getting out of a comment bloc, we should find
|
||||
# the name of the function
|
||||
assert len(line.split()) >= 1
|
||||
assert len(line.split()) >= 1, "Malformed line %s in %s" % (i, self.filename)
|
||||
current_block["line"] = i
|
||||
current_block["name"] = line.split()[0].strip("(){")
|
||||
# Then we expect to read the function
|
||||
|
@ -143,11 +147,11 @@ class Parser():
|
|||
b["usage"] = b["usage"].strip()
|
||||
|
||||
|
||||
|
||||
def is_global_comment(line):
|
||||
return line.startswith('#')
|
||||
|
||||
def malformed_error(line_number):
|
||||
import pdb; pdb.set_trace()
|
||||
return "Malformed file line {} ?".format(line_number)
|
||||
|
||||
def main():
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
"app_already_up_to_date": "{app:s} تم تحديثه مِن قَبل",
|
||||
"app_argument_choice_invalid": "",
|
||||
"app_argument_invalid": "",
|
||||
"app_argument_required": "",
|
||||
"app_change_no_change_url_script": "",
|
||||
"app_change_url_failed_nginx_reload": "",
|
||||
"app_argument_required": "المُعامِل '{name:s}' مطلوب",
|
||||
"app_change_no_change_url_script": "إنّ التطبيق {app_name:s} لا يدعم تغيير الرابط، مِن الممكن أنه يتوجب عليكم تحدثيه.",
|
||||
"app_change_url_failed_nginx_reload": "فشلت عملية إعادة تشغيل nginx. ها هي نتيجة الأمر 'nginx -t':\n{nginx_errors:s}",
|
||||
"app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.",
|
||||
"app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.",
|
||||
"app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}",
|
||||
|
@ -152,11 +152,11 @@
|
|||
"domain_dyndns_dynette_is_unreachable": "Unable to reach YunoHost dynette, either your YunoHost is not correctly connected to the internet or the dynette server is down. Error: {error}",
|
||||
"domain_dyndns_invalid": "Invalid domain to use with DynDNS",
|
||||
"domain_dyndns_root_unknown": "Unknown DynDNS root domain",
|
||||
"domain_exists": "Domain already exists",
|
||||
"domain_exists": "اسم النطاق موجود مِن قبل",
|
||||
"domain_hostname_failed": "Failed to set new hostname",
|
||||
"domain_uninstall_app_first": "One or more apps are installed on this domain. Please uninstall them before proceeding to domain removal",
|
||||
"domain_unknown": "النطاق مجهول",
|
||||
"domain_zone_exists": "DNS zone file already exists",
|
||||
"domain_zone_exists": "ملف منطقة أسماء النطاقات موجود مِن قبل",
|
||||
"domain_zone_not_found": "DNS zone file not found for domain {:s}",
|
||||
"domains_available": "النطاقات المتوفرة :",
|
||||
"done": "تم",
|
||||
|
@ -166,9 +166,9 @@
|
|||
"dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job",
|
||||
"dyndns_cron_removed": "The DynDNS cron job has been removed",
|
||||
"dyndns_ip_update_failed": "Unable to update IP address on DynDNS",
|
||||
"dyndns_ip_updated": "Your IP address has been updated on DynDNS",
|
||||
"dyndns_key_generating": "DNS key is being generated, it may take a while...",
|
||||
"dyndns_key_not_found": "DNS key not found for the domain",
|
||||
"dyndns_ip_updated": "لقد تم تحديث عنوان الإيبي الخاص بك على نظام أسماء النطاقات الديناميكي",
|
||||
"dyndns_key_generating": "عملية توليد مفتاح نظام أسماء النطاقات جارية. يمكن للعملية أن تستغرق بعضا من الوقت…",
|
||||
"dyndns_key_not_found": "لم يتم العثور على مفتاح DNS الخاص باسم النطاق هذا",
|
||||
"dyndns_no_domain_registered": "No domain has been registered with DynDNS",
|
||||
"dyndns_registered": "The DynDNS domain has been registered",
|
||||
"dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}",
|
||||
|
@ -380,5 +380,38 @@
|
|||
"service_description_nginx": "يقوم بتوفير النفاذ و السماح بالوصول إلى كافة مواقع الويب المستضافة على خادومك",
|
||||
"service_description_php5-fpm": "يقوم بتشغيل تطبيقات الـ PHP مع خادوم الويب nginx",
|
||||
"service_description_postfix": "يقوم بإرسال و تلقي الرسائل البريدية الإلكترونية",
|
||||
"service_description_yunohost-api": "يقوم بإدارة التفاعلات ما بين واجهة الويب لواي يونوهوست و النظام"
|
||||
"service_description_yunohost-api": "يقوم بإدارة التفاعلات ما بين واجهة الويب لواي يونوهوست و النظام",
|
||||
"log_category_404": "فئةالسجل '{category}' لا وجود لها",
|
||||
"log_app_fetchlist": "إضافة قائمة للتطبيقات",
|
||||
"log_app_removelist": "حذف قائمة للتطبيقات",
|
||||
"log_app_change_url": "تعديل رابط تطبيق '{}'",
|
||||
"log_app_install": "تنصيب تطبيق '{}'",
|
||||
"log_app_remove": "حذف تطبيق '{}'",
|
||||
"log_app_upgrade": "تحديث تطبيق '{}'",
|
||||
"log_app_makedefault": "تعيين '{}' كتطبيق افتراضي",
|
||||
"log_available_on_yunopaste": "هذا السجل متوفر الآن على {url}",
|
||||
"log_backup_restore_system": "استرجاع النظام مِن نسخة احتياطية",
|
||||
"log_backup_restore_app": "استرجاع '{}' مِن نسخة احتياطية",
|
||||
"log_remove_on_failed_install": "حذف '{}' بعد فشل التنصيب",
|
||||
"log_domain_add": "إضافة النطاق '{}' إلى إعدادات النظام",
|
||||
"log_domain_remove": "حذف النطاق '{}' مِن إعدادات النظام",
|
||||
"log_dyndns_subscribe": "تسجيل اسم نطاق واي يونوهوست فرعي '{}'",
|
||||
"log_dyndns_update": "تحديث عنوان الإيبي ذي الصلة مع اسم النطاق الفرعي واي يونوهوست '{}'",
|
||||
"log_letsencrypt_cert_install": "تنصيب شهادة Let’s Encrypt على النطاق '{}'",
|
||||
"log_selfsigned_cert_install": "تنصيب شهادة موقَّعَة ذاتيا على اسم النطاق '{}'",
|
||||
"log_letsencrypt_cert_renew": "تجديد شهادة Let's Encrypt لـ '{}'",
|
||||
"log_service_enable": "تنشيط خدمة '{}'",
|
||||
"log_user_create": "إضافة المستخدم '{}'",
|
||||
"log_user_delete": "حذف المستخدم '{}'",
|
||||
"log_user_update": "تحديث معلومات المستخدم '{}'",
|
||||
"log_tools_maindomain": "جعل '{}' كنطاق أساسي",
|
||||
"log_tools_upgrade": "تحديث حُزم ديبيان",
|
||||
"log_tools_shutdown": "إطفاء الخادم",
|
||||
"log_tools_reboot": "إعادة تشغيل الخادم",
|
||||
"migration_description_0005_postgresql_9p4_to_9p6": "تهجير قواعد البيانات مِن postgresql 9.4 إلى 9.6",
|
||||
"service_description_dnsmasq": "مُكلَّف بتحليل أسماء النطاقات (DNS)",
|
||||
"service_description_mysql": "يقوم بتخزين بيانات التطبيقات (قواعد بيانات SQL)",
|
||||
"service_description_rspamd": "يقوم بتصفية البريد المزعج و إدارة ميزات أخرى للبريد",
|
||||
"service_description_yunohost-firewall": "يريد فتح و غلق منافذ الإتصال إلى الخدمات",
|
||||
"users_available": "المستخدمون المتوفرون:"
|
||||
}
|
||||
|
|
25
locales/ca.json
Normal file
25
locales/ca.json
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"action_invalid": "Acció '{action:s}' invàlida",
|
||||
"admin_password": "Contrasenya d'administració",
|
||||
"admin_password_change_failed": "No s'ha pogut canviar la contrasenya",
|
||||
"admin_password_changed": "S'ha canviat la contrasenya d'administració",
|
||||
"app_already_installed": "{app:s} ja està instal·lada",
|
||||
"app_already_installed_cant_change_url": "Aquesta aplicació ja està instal·lada. La URL no és pot canviar únicament amb aquesta funció. Mireu a \"app changeurl\" si està disponible.",
|
||||
"app_already_up_to_date": "{app:s} ja està actualitzada",
|
||||
"app_argument_choice_invalid": "Aquesta opció no és vàlida per l'argument '{name:s}', ha de ser una de {choices:s}",
|
||||
"app_argument_invalid": "Valor invàlid per l'argument '{name:s}':{error:s}",
|
||||
"app_argument_required": "Es necessita l'argument '{name:s}'",
|
||||
"app_change_no_change_url_script": "L'aplicació {app_name:s} encara no permet canviar la seva URL, es possible que s'hagi d'actualitzar.",
|
||||
"app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar nginx. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors:s}",
|
||||
"app_change_url_identical_domains": "L'antic i el nou domini/camí són idèntics ('{domain:s}{path:s}'), no hi ha res per fer.",
|
||||
"app_change_url_no_script": "Aquesta aplicació '{app_name:s}' encara no permet modificar la URL. Potser s'ha d'actualitzar l'aplicació.",
|
||||
"app_change_url_success": "La URL de {app:s} s'ha canviat correctament a {domain:s}{path:s}",
|
||||
"app_extraction_failed": "No s'han pogut extreure els fitxers d'instal·lació",
|
||||
"app_id_invalid": "Id de l'aplicació incorrecte",
|
||||
"app_incompatible": "L'aplicació {app} no és compatible amb la teva versió de YunoHost",
|
||||
"app_install_files_invalid": "Fitxers d'instal·lació invàlids",
|
||||
"app_location_already_used": "L'aplicació '{app}' ja està instal·lada en aquest camí ({path})",
|
||||
"app_make_default_location_already_used": "No es pot fer l'aplicació '{app}' per defecte en el domini {domain} ja que ja és utilitzat per una altra aplicació '{other_app}'",
|
||||
"app_location_install_failed": "No s'ha pogut instal·lar l'aplicació en aquest camí ja que entra en conflicte amb l'aplicació '{other_app}' ja instal·lada a '{other_path}'",
|
||||
"app_location_unavailable": "Aquesta url no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps:s}"
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"aborting": "Aborting.",
|
||||
"action_invalid": "Invalid action '{action:s}'",
|
||||
"admin_password": "Administration password",
|
||||
"admin_password_change_failed": "Unable to change password",
|
||||
|
@ -132,6 +133,9 @@
|
|||
"certmanager_no_cert_file": "Unable to read certificate file for domain {domain:s} (file: {file:s})",
|
||||
"certmanager_self_ca_conf_file_not_found": "Configuration file not found for self-signing authority (file: {file:s})",
|
||||
"certmanager_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority (file: {file:s})",
|
||||
"confirm_app_install_warning": "Warning: this application may work but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers:s}] ",
|
||||
"confirm_app_install_danger": "WARNING! This application is still experimental (if not explicitly not working) and it is likely to break your system! You should probably NOT install it unless you know what you are doing. Are you willing to take that risk? [{answers:s}] ",
|
||||
"confirm_app_install_thirdparty": "WARNING! Installing 3rd party applications may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. Are you willing to take that risk? [{answers:s}] ",
|
||||
"custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}",
|
||||
"custom_appslist_name_required": "You must provide a name for your custom app list",
|
||||
"diagnosis_debian_version_error": "Can't retrieve the Debian version: {error}",
|
||||
|
@ -194,8 +198,13 @@
|
|||
"global_settings_setting_example_enum": "Example enum option",
|
||||
"global_settings_setting_example_int": "Example int option",
|
||||
"global_settings_setting_example_string": "Example string option",
|
||||
"global_settings_setting_security_password_admin_strength": "Admin password strength",
|
||||
"global_settings_setting_security_password_user_strength": "User password strength",
|
||||
"global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/unkown_settings.json",
|
||||
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration",
|
||||
"global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.",
|
||||
"good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind 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 - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",
|
||||
"hook_exec_failed": "Script execution failed: {path:s}",
|
||||
"hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}",
|
||||
"hook_list_by_invalid": "Invalid property to list hook by",
|
||||
|
@ -209,7 +218,7 @@
|
|||
"log_category_404": "The log category '{category}' does not exist",
|
||||
"log_link_to_log": "Full log of this operation: '<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>'",
|
||||
"log_help_to_get_log": "To view the log of the operation '{desc}', use the command 'yunohost log display {name}'",
|
||||
"log_link_to_failed_log": "The operation '{desc}' has failed ! To get help, please <a href=\"#/tools/logs/{name}\">provide the full log of this operation</a>",
|
||||
"log_link_to_failed_log": "The operation '{desc}' has failed ! To get help, please <a href=\"#/tools/logs/{name}\">provide the full log of this operation by clicking here</a>",
|
||||
"log_help_to_get_failed_log": "The operation '{desc}' has failed ! To get help, please share the full log of this operation using the command 'yunohost log display {name} --share'",
|
||||
"log_category_404": "The log category '{category}' does not exist",
|
||||
"log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list to see all available operation logs'",
|
||||
|
@ -255,6 +264,7 @@
|
|||
"mail_domain_unknown": "Unknown mail address domain '{domain:s}'",
|
||||
"mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'",
|
||||
"mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space",
|
||||
"mail_unavailable": "This email address is reserved and shall be automatically allocated to the very first user",
|
||||
"maindomain_change_failed": "Unable to change the main domain",
|
||||
"maindomain_changed": "The main domain has been changed",
|
||||
"migrate_tsig_end": "Migration to hmac-sha512 finished",
|
||||
|
@ -270,6 +280,9 @@
|
|||
"migration_description_0003_migrate_to_stretch": "Upgrade the system to Debian Stretch and YunoHost 3.0",
|
||||
"migration_description_0004_php5_to_php7_pools": "Reconfigure the PHP pools to use PHP 7 instead of 5",
|
||||
"migration_description_0005_postgresql_9p4_to_9p6": "Migrate databases from postgresql 9.4 to 9.6",
|
||||
"migration_description_0006_sync_admin_and_root_passwords": "Synchronize admin and root passwords",
|
||||
"migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Let the SSH configuration be managed by YunoHost (step 1, automatic)",
|
||||
"migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)",
|
||||
"migration_0003_backward_impossible": "The stretch migration cannot be reverted.",
|
||||
"migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.",
|
||||
"migration_0003_patching_sources_list": "Patching the sources.lists ...",
|
||||
|
@ -286,6 +299,15 @@
|
|||
"migration_0005_postgresql_94_not_installed": "Postgresql was not installed on your system. Nothing to do!",
|
||||
"migration_0005_postgresql_96_not_installed": "Postgresql 9.4 has been found to be installed, but not postgresql 9.6 !? Something weird might have happened on your system :( ...",
|
||||
"migration_0005_not_enough_space": "Not enough space is available in {path} to run the migration right now :(.",
|
||||
"migration_0006_disclaimer": "Yunohost now expects admin and root passwords to be synchronized. By running this migration, your root password is going to be replaced by the admin password.",
|
||||
"migration_0007_cancelled": "YunoHost has failed to improve the way your SSH conf is managed.",
|
||||
"migration_0007_cannot_restart": "SSH can't be restarted after trying to cancel migration number 6.",
|
||||
"migration_0008_general_disclaimer": "To improve the security of your server, it is recommended to let YunoHost manage the SSH configuration. Your current SSH configuration differs from the recommended configuration. If you let YunoHost reconfigure it, the way you connect to your server through SSH will change in the following way:",
|
||||
"migration_0008_port": " - you will have to connect using port 22 instead of your current custom SSH port. Feel free to reconfigure it ;",
|
||||
"migration_0008_root": " - you will not be able to connect as root through SSH. Instead you should use the admin user ;",
|
||||
"migration_0008_dsa": " - the DSA key will be disabled. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server ;",
|
||||
"migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.",
|
||||
"migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;) ! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.",
|
||||
"migrations_backward": "Migrating backward.",
|
||||
"migrations_bad_value_for_target": "Invalid number for target argument, available migrations numbers are 0 or {}",
|
||||
"migrations_cant_reach_migration_file": "Can't access migrations files at path %s",
|
||||
|
@ -299,6 +321,7 @@
|
|||
"migrations_show_currently_running_migration": "Running migration {number} {name}...",
|
||||
"migrations_show_last_migration": "Last ran migration is {}",
|
||||
"migrations_skip_migration": "Skipping migration {number} {name}...",
|
||||
"migrations_success": "Successfully ran migration {number} {name}!",
|
||||
"migrations_to_be_ran_manually": "Migration {number} {name} has to be ran manually. Please go to Tools > Migrations on the webadmin, or run `yunohost tools migrations migrate`.",
|
||||
"migrations_need_to_accept_disclaimer": "To run the migration {number} {name}, your must accept the following disclaimer:\n---\n{disclaimer}\n---\nIf you accept to run the migration, please re-run the command with the option --accept-disclaimer.",
|
||||
"monitor_disabled": "The server monitoring has been disabled",
|
||||
|
@ -328,6 +351,11 @@
|
|||
"packages_no_upgrade": "There is no package to upgrade",
|
||||
"packages_upgrade_critical_later": "Critical packages ({packages:s}) will be upgraded later",
|
||||
"packages_upgrade_failed": "Unable to upgrade all of the packages",
|
||||
"password_listed": "This password is among the most used password in the world. Please choose something a bit more unique.",
|
||||
"password_too_simple_1": "Password needs to be at least 8 characters long",
|
||||
"password_too_simple_2": "Password needs to be at least 8 characters long and contains digit, upper and lower characters",
|
||||
"password_too_simple_3": "Password needs to be at least 8 characters long and contains digit, upper, lower and special characters",
|
||||
"password_too_simple_4": "Password needs to be at least 12 characters long and contains digit, upper, lower and special characters",
|
||||
"path_removal_failed": "Unable to remove path {:s}",
|
||||
"pattern_backup_archive_name": "Must be a valid filename with max 30 characters, and alphanumeric and -_. characters only",
|
||||
"pattern_domain": "Must be a valid domain name (e.g. my-domain.org)",
|
||||
|
@ -363,6 +391,8 @@
|
|||
"restore_running_app_script": "Running restore script of app '{app:s}'...",
|
||||
"restore_running_hooks": "Running restoration hooks...",
|
||||
"restore_system_part_failed": "Unable to restore the '{part:s}' system part",
|
||||
"root_password_desynchronized": "The admin password has been changed, but YunoHost was unable to propagate this on the root password !",
|
||||
"root_password_replaced_by_admin_password": "Your root password have been replaced by your admin password.",
|
||||
"server_shutdown": "The server will shutdown",
|
||||
"server_shutdown_confirm": "The server will shutdown immediatly, are you sure? [{answers:s}]",
|
||||
"server_reboot": "The server will reboot",
|
||||
|
@ -380,7 +410,7 @@
|
|||
"service_conf_file_remove_failed": "Unable to remove the configuration file '{conf}'",
|
||||
"service_conf_file_removed": "The configuration file '{conf}' has been removed",
|
||||
"service_conf_file_updated": "The configuration file '{conf}' has been updated",
|
||||
"service_conf_new_managed_file": "The configuration file '{conf}' is now managed by the service {service}.",
|
||||
"service_conf_now_managed_by_yunohost": "The configuration file '{conf}' is now managed by YunoHost.",
|
||||
"service_conf_up_to_date": "The configuration is already up-to-date for service '{service}'",
|
||||
"service_conf_updated": "The configuration has been updated for service '{service}'",
|
||||
"service_conf_would_be_updated": "The configuration would have been updated for service '{service}'",
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
"dyndns_no_domain_registered": "Ningún dominio ha sido registrado con DynDNS",
|
||||
"dyndns_registered": "El dominio DynDNS ha sido registrado",
|
||||
"dyndns_registration_failed": "No se pudo registrar el dominio DynDNS: {error:s}",
|
||||
"dyndns_unavailable": "El subdominio DynDNS no está disponible",
|
||||
"dyndns_unavailable": "El dominio {domain:s} no está disponible.",
|
||||
"executing_command": "Ejecutando el comando '{command:s}'...",
|
||||
"executing_script": "Ejecutando el script '{script:s}'...",
|
||||
"extracting": "Extrayendo...",
|
||||
|
@ -174,7 +174,7 @@
|
|||
"restore_complete": "Restauración finalizada",
|
||||
"restore_confirm_yunohost_installed": "¿Realmente desea restaurar un sistema ya instalado? [{answers:s}]",
|
||||
"restore_failed": "No se pudo restaurar el sistema",
|
||||
"restore_hook_unavailable": "El hook de restauración '{hook:s}' no está disponible en su sistema",
|
||||
"restore_hook_unavailable": "El script de restauración '{part:s}' no está disponible en su sistema y tampoco en el archivo",
|
||||
"restore_nothings_done": "No se ha restaurado nada",
|
||||
"restore_running_app_script": "Ejecutando el script de restauración de la aplicación '{app:s}'...",
|
||||
"restore_running_hooks": "Ejecutando los hooks de restauración...",
|
||||
|
@ -204,7 +204,7 @@
|
|||
"service_regenconf_pending_applying": "Aplicando la configuración pendiente para el servicio '{service}'...",
|
||||
"service_remove_failed": "No se pudo desinstalar el servicio '{service:s}'",
|
||||
"service_removed": "El servicio '{service:s}' ha sido desinstalado",
|
||||
"service_start_failed": "No se pudo iniciar el servicio '{service:s}'",
|
||||
"service_start_failed": "No se pudo iniciar el servicio '{service:s}'\n\nRegistros de servicio recientes : {logs:s}",
|
||||
"service_started": "El servicio '{service:s}' ha sido iniciado",
|
||||
"service_status_failed": "No se pudo determinar el estado del servicio '{service:s}'",
|
||||
"service_stop_failed": "No se pudo detener el servicio '{service:s}'",
|
||||
|
@ -281,7 +281,7 @@
|
|||
"app_change_url_identical_domains": "El antiguo y nuevo dominio/url_path son idénticos ('{domain:s} {path:s}'), no se realizarán cambios.",
|
||||
"app_change_url_no_script": "Esta aplicación '{app_name:s}' aún no permite modificar su URL. Quizás debería actualizar la aplicación.",
|
||||
"app_change_url_success": "El URL de la aplicación {app:s} ha sido cambiado correctamente a {domain:s} {path:s}",
|
||||
"app_location_unavailable": "Este URL no está disponible o está en conflicto con otra aplicación instalada",
|
||||
"app_location_unavailable": "Este URL no está disponible o está en conflicto con otra aplicación instalada:\n{apps:s}",
|
||||
"app_already_up_to_date": "La aplicación {app:s} ya está actualizada",
|
||||
"appslist_name_already_tracked": "Ya existe una lista de aplicaciones registrada con el nombre {name:s}.",
|
||||
"appslist_url_already_tracked": "Ya existe una lista de aplicaciones registrada con el URL {url:s}.",
|
||||
|
@ -307,5 +307,15 @@
|
|||
"backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar el archivo",
|
||||
"backup_couldnt_bind": "No puede enlazar {src:s} con {dest:s}",
|
||||
"backup_csv_addition_failed": "No puede añadir archivos al backup en el archivo CSV",
|
||||
"backup_csv_creation_failed": "No se puede crear el archivo CSV necesario para futuras operaciones de restauración"
|
||||
"backup_csv_creation_failed": "No se puede crear el archivo CSV necesario para futuras operaciones de restauración",
|
||||
"backup_custom_mount_error": "Fracaso del método de copia de seguridad personalizada en la etapa \"mount\"",
|
||||
"backup_custom_need_mount_error": "Fracaso del método de copia de seguridad personalizada en la étapa \"need_mount\"",
|
||||
"backup_no_uncompress_archive_dir": "El directorio del archivo descomprimido no existe",
|
||||
"backup_php5_to_php7_migration_may_fail": "No se ha podido convertir su archivo para soportar php7, la restauración de sus aplicaciones php puede fallar (razón : {error:s})",
|
||||
"backup_system_part_failed": "No se puede hacer una copia de seguridad de la parte \"{part:s}\" del sistema",
|
||||
"backup_with_no_backup_script_for_app": "La aplicación {app:s} no tiene script de respaldo. Se ha ignorado.",
|
||||
"backup_with_no_restore_script_for_app": "La aplicación {app:s} no tiene script de restauración, no podrá restaurar automáticamente la copia de seguridad de esta aplicación.",
|
||||
"dyndns_could_not_check_provide": "No se pudo verificar si {provider:s} puede ofrecer {domain:s}.",
|
||||
"dyndns_domain_not_provided": "El proveedor Dyndns {provider:s} no puede proporcionar el dominio {domain:s}.",
|
||||
"experimental_feature": "Cuidado : esta funcionalidad es experimental y no es considerada estable, no debería usarla excepto si sabe lo que hace."
|
||||
}
|
||||
|
|
232
locales/fr.json
232
locales/fr.json
|
@ -1,25 +1,25 @@
|
|||
{
|
||||
"action_invalid": "Action « {action:s} » incorrecte",
|
||||
"admin_password": "Mot de passe d'administration",
|
||||
"admin_password": "Mot de passe d’administration",
|
||||
"admin_password_change_failed": "Impossible de changer le mot de passe",
|
||||
"admin_password_changed": "Le mot de passe d'administration a été modifié",
|
||||
"admin_password_changed": "Le mot de passe d’administration a été modifié",
|
||||
"app_already_installed": "{app:s} est déjà installé",
|
||||
"app_argument_choice_invalid": "Choix invalide pour le paramètre « {name:s} », il doit être l'un de {choices:s}",
|
||||
"app_argument_choice_invalid": "Choix invalide pour le paramètre « {name:s} », il doit être l’un de {choices:s}",
|
||||
"app_argument_invalid": "Valeur invalide pour le paramètre « {name:s} » : {error:s}",
|
||||
"app_argument_missing": "Paramètre manquant « {:s} »",
|
||||
"app_argument_required": "Le paramètre « {name:s} » est requis",
|
||||
"app_extraction_failed": "Impossible d'extraire les fichiers d'installation",
|
||||
"app_id_invalid": "Id d'application incorrect",
|
||||
"app_incompatible": "L'application {app} est incompatible avec votre version de YunoHost",
|
||||
"app_install_files_invalid": "Fichiers d'installation incorrects",
|
||||
"app_location_already_used": "L'application '{app}' est déjà installée à cet emplacement ({path})",
|
||||
"app_location_install_failed": "Impossible d'installer l'application à cet emplacement pour cause de conflit avec l'app '{other_app}' déjà installée sur '{other_path}'",
|
||||
"app_manifest_invalid": "Manifeste d'application incorrect : {error}",
|
||||
"app_extraction_failed": "Impossible d’extraire les fichiers d’installation",
|
||||
"app_id_invalid": "Id d’application incorrect",
|
||||
"app_incompatible": "L’application {app} est incompatible avec votre version de YunoHost",
|
||||
"app_install_files_invalid": "Fichiers d’installation incorrects",
|
||||
"app_location_already_used": "L’application « {app} » est déjà installée à cet emplacement ({path})",
|
||||
"app_location_install_failed": "Impossible d’installer l’application à cet emplacement pour cause de conflit avec l’app « {other_app} » déjà installée sur « {other_path} »",
|
||||
"app_manifest_invalid": "Manifeste d’application incorrect : {error}",
|
||||
"app_no_upgrade": "Aucune application à mettre à jour",
|
||||
"app_not_correctly_installed": "{app:s} semble être mal installé",
|
||||
"app_not_installed": "{app:s} n'est pas installé",
|
||||
"app_not_properly_removed": "{app:s} n'a pas été supprimé correctement",
|
||||
"app_package_need_update": "Le paquet de l'application {app} doit être mis à jour pour suivre les changements de YunoHost",
|
||||
"app_not_installed": "{app:s} n’est pas installé",
|
||||
"app_not_properly_removed": "{app:s} n’a pas été supprimé correctement",
|
||||
"app_package_need_update": "Le paquet de l’application {app} doit être mis à jour pour suivre les changements de YunoHost",
|
||||
"app_recent_version_required": "{app:s} nécessite une version plus récente de YunoHost",
|
||||
"app_removed": "{app:s} a été supprimé",
|
||||
"app_requirements_checking": "Vérification des paquets requis pour {app}...",
|
||||
|
@ -27,45 +27,45 @@
|
|||
"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",
|
||||
"app_unknown": "Application inconnue",
|
||||
"app_unsupported_remote_type": "Le type distant utilisé par l'application n'est pas supporté",
|
||||
"app_unsupported_remote_type": "Le type distant utilisé par l’application n’est pas pris en charge",
|
||||
"app_upgrade_failed": "Impossible de mettre à jour {app:s}",
|
||||
"app_upgraded": "{app:s} a été mis à jour",
|
||||
"appslist_fetched": "La liste d’applications {appslist:s} a été récupérée",
|
||||
"appslist_removed": "La liste d’applications {appslist:s} a été supprimée",
|
||||
"appslist_retrieve_error": "Impossible de récupérer la liste d’applications distante {appslist:s} : {error:s}",
|
||||
"appslist_unknown": "La liste d’applications {appslist:s} est inconnue.",
|
||||
"ask_current_admin_password": "Mot de passe d'administration actuel",
|
||||
"ask_current_admin_password": "Mot de passe d’administration actuel",
|
||||
"ask_email": "Adresse courriel",
|
||||
"ask_firstname": "Prénom",
|
||||
"ask_lastname": "Nom",
|
||||
"ask_list_to_remove": "Liste à supprimer",
|
||||
"ask_main_domain": "Domaine principal",
|
||||
"ask_new_admin_password": "Nouveau mot de passe d'administration",
|
||||
"ask_new_admin_password": "Nouveau mot de passe d’administration",
|
||||
"ask_password": "Mot de passe",
|
||||
"backup_action_required": "Vous devez préciser ce qui est à sauvegarder",
|
||||
"backup_app_failed": "Impossible de sauvegarder l'application « {app:s} »",
|
||||
"backup_archive_app_not_found": "L'application « {app:s} » n'a pas été trouvée dans l'archive de la sauvegarde",
|
||||
"backup_app_failed": "Impossible de sauvegarder l’application « {app:s} »",
|
||||
"backup_archive_app_not_found": "L’application « {app:s} » n’a pas été trouvée dans l’archive de la sauvegarde",
|
||||
"backup_archive_hook_not_exec": "Le script « {hook:s} » n'a pas été exécuté dans cette sauvegarde",
|
||||
"backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà",
|
||||
"backup_archive_name_unknown": "L'archive locale de sauvegarde nommée « {name:s} » est inconnue",
|
||||
"backup_archive_open_failed": "Impossible d'ouvrir l'archive de sauvegarde",
|
||||
"backup_archive_name_unknown": "L’archive locale de sauvegarde nommée « {name:s} » est inconnue",
|
||||
"backup_archive_open_failed": "Impossible d’ouvrir l’archive de sauvegarde",
|
||||
"backup_cleaning_failed": "Impossible de nettoyer le dossier temporaire de sauvegarde",
|
||||
"backup_created": "Sauvegarde terminée",
|
||||
"backup_creating_archive": "Création de l'archive de sauvegarde...",
|
||||
"backup_creating_archive": "Création de l’archive de sauvegarde...",
|
||||
"backup_creation_failed": "Impossible de créer la sauvegarde",
|
||||
"backup_delete_error": "Impossible de supprimer « {path:s} »",
|
||||
"backup_deleted": "La sauvegarde a été supprimée",
|
||||
"backup_extracting_archive": "Extraction de l'archive de sauvegarde...",
|
||||
"backup_extracting_archive": "Extraction de l’archive de sauvegarde...",
|
||||
"backup_hook_unknown": "Script de sauvegarde « {hook:s} » inconnu",
|
||||
"backup_invalid_archive": "Archive de sauvegarde incorrecte",
|
||||
"backup_nothings_done": "Il n'y a rien à sauvegarder",
|
||||
"backup_nothings_done": "Il n’y a rien à sauvegarder",
|
||||
"backup_output_directory_forbidden": "Dossier de destination interdit. Les sauvegardes ne peuvent être créées dans les dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives",
|
||||
"backup_output_directory_not_empty": "Le dossier de sortie n'est pas vide",
|
||||
"backup_output_directory_not_empty": "Le dossier de sortie n’est pas vide",
|
||||
"backup_output_directory_required": "Vous devez spécifier un dossier de sortie pour la sauvegarde",
|
||||
"backup_running_app_script": "Lancement du script de sauvegarde de l'application « {app:s} »...",
|
||||
"backup_running_app_script": "Lancement du script de sauvegarde de l’application « {app:s} »...",
|
||||
"backup_running_hooks": "Exécution des scripts de sauvegarde...",
|
||||
"custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application locale {app:s}",
|
||||
"custom_appslist_name_required": "Vous devez spécifier un nom pour votre liste d'applications personnalisée",
|
||||
"custom_appslist_name_required": "Vous devez spécifier un nom pour votre liste d’applications personnalisée",
|
||||
"diagnosis_debian_version_error": "Impossible de déterminer la version de Debian : {error}",
|
||||
"diagnosis_kernel_version_error": "Impossible de récupérer la version du noyau : {error}",
|
||||
"diagnosis_monitor_disk_error": "Impossible de superviser les disques : {error}",
|
||||
|
@ -82,22 +82,22 @@
|
|||
"domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS",
|
||||
"domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu",
|
||||
"domain_exists": "Le domaine existe déjà",
|
||||
"domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d'abord les désinstaller avant de supprimer ce domaine",
|
||||
"domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d’abord les désinstaller avant de supprimer ce domaine",
|
||||
"domain_unknown": "Domaine inconnu",
|
||||
"domain_zone_exists": "Le fichier de zone DNS existe déjà",
|
||||
"domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}",
|
||||
"done": "Terminé",
|
||||
"downloading": "Téléchargement...",
|
||||
"dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été installée",
|
||||
"dyndns_cron_remove_failed": "Impossible d'enlever la tâche cron pour le domaine DynDNS",
|
||||
"dyndns_cron_remove_failed": "Impossible d’enlever la tâche cron pour le domaine DynDNS",
|
||||
"dyndns_cron_removed": "La tâche cron pour le domaine DynDNS a été enlevée",
|
||||
"dyndns_ip_update_failed": "Impossible de mettre à jour l'adresse IP sur le domaine DynDNS",
|
||||
"dyndns_ip_update_failed": "Impossible de mettre à jour l’adresse IP sur le domaine DynDNS",
|
||||
"dyndns_ip_updated": "Votre adresse IP a été mise à jour pour le domaine DynDNS",
|
||||
"dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre du temps...",
|
||||
"dyndns_key_not_found": "Clé DNS introuvable pour le domaine",
|
||||
"dyndns_no_domain_registered": "Aucun domaine n'a été enregistré avec DynDNS",
|
||||
"dyndns_no_domain_registered": "Aucun domaine n’a été enregistré avec DynDNS",
|
||||
"dyndns_registered": "Le domaine DynDNS a été enregistré",
|
||||
"dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {error:s}",
|
||||
"dyndns_registration_failed": "Impossible d’enregistrer le domaine DynDNS : {error:s}",
|
||||
"dyndns_unavailable": "Le domaine {domain:s} est indisponible.",
|
||||
"executing_command": "Exécution de la commande « {command:s} »...",
|
||||
"executing_script": "Exécution du script « {script:s} »...",
|
||||
|
@ -105,7 +105,7 @@
|
|||
"field_invalid": "Champ incorrect : « {:s} »",
|
||||
"firewall_reload_failed": "Impossible de recharger le pare-feu",
|
||||
"firewall_reloaded": "Le pare-feu a été rechargé",
|
||||
"firewall_rules_cmd_failed": "Certaines règles du pare-feu n'ont pas pu être appliquées. Pour plus d'informations, consultez le journal.",
|
||||
"firewall_rules_cmd_failed": "Certaines règles du pare-feu n’ont pas pu être appliquées. Pour plus d’informations, consultez le journal.",
|
||||
"format_datetime_short": "%d/%m/%Y %H:%M",
|
||||
"hook_argument_missing": "Argument manquant : '{:s}'",
|
||||
"hook_choice_invalid": "Choix incorrect : '{:s}'",
|
||||
|
@ -114,12 +114,12 @@
|
|||
"hook_list_by_invalid": "La propriété de tri des actions est invalide",
|
||||
"hook_name_unknown": "Nom de script « {name:s} » inconnu",
|
||||
"installation_complete": "Installation terminée",
|
||||
"installation_failed": "Échec de l'installation",
|
||||
"ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le supporte pas",
|
||||
"iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le supporte pas",
|
||||
"ldap_initialized": "L'annuaire LDAP a été initialisé",
|
||||
"installation_failed": "Échec de l’installation",
|
||||
"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": "L’annuaire LDAP a été initialisé",
|
||||
"license_undefined": "indéfinie",
|
||||
"mail_alias_remove_failed": "Impossible de supprimer l'alias courriel « {mail:s} »",
|
||||
"mail_alias_remove_failed": "Impossible de supprimer l’alias courriel « {mail:s} »",
|
||||
"mail_domain_unknown": "Le domaine « {domain:s} » du courriel est inconnu",
|
||||
"mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert « {mail:s} »",
|
||||
"maindomain_change_failed": "Impossible de modifier le domaine principal",
|
||||
|
@ -127,29 +127,29 @@
|
|||
"monitor_disabled": "La supervision du serveur a été désactivé",
|
||||
"monitor_enabled": "La supervision du serveur a été activé",
|
||||
"monitor_glances_con_failed": "Impossible de se connecter au serveur Glances",
|
||||
"monitor_not_enabled": "Le suivi de l'état du serveur n'est pas activé",
|
||||
"monitor_not_enabled": "Le suivi de l’état du serveur n’est pas activé",
|
||||
"monitor_period_invalid": "Période de temps incorrecte",
|
||||
"monitor_stats_file_not_found": "Le fichier de statistiques est introuvable",
|
||||
"monitor_stats_no_update": "Aucune donnée de l'état du serveur à mettre à jour",
|
||||
"monitor_stats_period_unavailable": "Aucune statistique n'est disponible pour la période",
|
||||
"monitor_stats_no_update": "Aucune donnée de l’état du serveur à mettre à jour",
|
||||
"monitor_stats_period_unavailable": "Aucune statistique n’est disponible pour la période",
|
||||
"mountpoint_unknown": "Point de montage inconnu",
|
||||
"mysql_db_creation_failed": "Impossible de créer la base de données MySQL",
|
||||
"mysql_db_init_failed": "Impossible d'initialiser la base de données MySQL",
|
||||
"mysql_db_init_failed": "Impossible d’initialiser la base de données MySQL",
|
||||
"mysql_db_initialized": "La base de données MySQL a été initialisée",
|
||||
"network_check_mx_ko": "L'enregistrement DNS MX n'est pas précisé",
|
||||
"network_check_mx_ko": "L’enregistrement DNS MX n’est pas précisé",
|
||||
"network_check_smtp_ko": "Le trafic courriel sortant (port 25 SMTP) semble bloqué par votre réseau",
|
||||
"network_check_smtp_ok": "Le trafic courriel sortant (port 25 SMTP) n'est pas bloqué",
|
||||
"network_check_smtp_ok": "Le trafic courriel sortant (port 25 SMTP) n’est pas bloqué",
|
||||
"new_domain_required": "Vous devez spécifier le nouveau domaine principal",
|
||||
"no_appslist_found": "Aucune liste d’applications n’a été trouvée",
|
||||
"no_internet_connection": "Le serveur n'est pas connecté à Internet",
|
||||
"no_ipv6_connectivity": "La connectivité IPv6 n'est pas disponible",
|
||||
"no_restore_script": "Le script de sauvegarde n'a pas été trouvé pour l'application « {app:s} »",
|
||||
"no_internet_connection": "Le serveur n’est pas connecté à Internet",
|
||||
"no_ipv6_connectivity": "La connectivité IPv6 n’est pas disponible",
|
||||
"no_restore_script": "Le script de sauvegarde n’a pas été trouvé pour l’application « {app:s} »",
|
||||
"no_such_conf_file": "Le fichier {file:s} n’existe pas, il ne peut pas être copié",
|
||||
"not_enough_disk_space": "L'espace disque est insuffisant sur « {path:s} »",
|
||||
"package_not_installed": "Le paquet « {pkgname} » n'est pas installé",
|
||||
"not_enough_disk_space": "L’espace disque est insuffisant sur « {path:s} »",
|
||||
"package_not_installed": "Le paquet « {pkgname} » n’est pas installé",
|
||||
"package_unexpected_error": "Une erreur inattendue est survenue avec le paquet « {pkgname} »",
|
||||
"package_unknown": "Paquet « {pkgname} » inconnu",
|
||||
"packages_no_upgrade": "Il n'y a aucun paquet à mettre à jour",
|
||||
"packages_no_upgrade": "Il n’y a aucun paquet à mettre à jour",
|
||||
"packages_upgrade_critical_later": "Les paquets critiques ({packages:s}) seront mis à jour ultérieurement",
|
||||
"packages_upgrade_failed": "Impossible de mettre à jour tous les paquets",
|
||||
"path_removal_failed": "Impossible de supprimer le chemin {:s}",
|
||||
|
@ -160,7 +160,7 @@
|
|||
"pattern_lastname": "Doit être un nom valide",
|
||||
"pattern_listname": "Doit être composé uniquement de caractères alphanumériques et de tirets bas",
|
||||
"pattern_mailbox_quota": "Doit être une taille avec le suffixe b/k/M/G/T ou 0 pour désactiver le quota",
|
||||
"pattern_password": "Doit être composé d'au moins 3 caractères",
|
||||
"pattern_password": "Doit être composé d’au moins 3 caractères",
|
||||
"pattern_port": "Doit être un numéro de port valide (ex. : 0-65535)",
|
||||
"pattern_port_or_range": "Doit être un numéro de port valide (ex. : 0-65535) ou une gamme de ports (ex. : 100:200)",
|
||||
"pattern_positive_number": "Doit être un nombre positif",
|
||||
|
@ -168,24 +168,24 @@
|
|||
"port_already_closed": "Le port {port:d} est déjà fermé pour les connexions {ip_version:s}",
|
||||
"port_already_opened": "Le port {port:d} est déjà ouvert pour les connexions {ip_version:s}",
|
||||
"port_available": "Le port {port:d} est disponible",
|
||||
"port_unavailable": "Le port {port:d} n'est pas disponible",
|
||||
"port_unavailable": "Le port {port:d} n’est pas disponible",
|
||||
"restore_action_required": "Vous devez préciser ce qui est à restaurer",
|
||||
"restore_already_installed_app": "Une application est déjà installée avec l'id « {app:s} »",
|
||||
"restore_app_failed": "Impossible de restaurer l'application « {app:s} »",
|
||||
"restore_already_installed_app": "Une application est déjà installée avec l’id « {app:s} »",
|
||||
"restore_app_failed": "Impossible de restaurer l’application « {app:s} »",
|
||||
"restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration",
|
||||
"restore_complete": "Restauration terminée",
|
||||
"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} » n'est pas disponible sur votre système, et n’est pas non plus dans l’archive",
|
||||
"restore_nothings_done": "Rien n'a été restauré",
|
||||
"restore_running_app_script": "Lancement du script de restauration pour l'application « {app:s} »...",
|
||||
"restore_hook_unavailable": "Le script de restauration « {part:s} » n’est pas disponible sur votre système, et n’est pas non plus dans l’archive",
|
||||
"restore_nothings_done": "Rien n’a été restauré",
|
||||
"restore_running_app_script": "Lancement du script de restauration pour l’application « {app:s} »...",
|
||||
"restore_running_hooks": "Exécution des scripts de restauration...",
|
||||
"service_add_configuration": "Ajout du fichier de configuration {file:s}",
|
||||
"service_add_failed": "Impossible d'ajouter le service « {service:s} »",
|
||||
"service_add_failed": "Impossible d’ajouter le service « {service:s} »",
|
||||
"service_added": "Le service « {service:s} » a été ajouté",
|
||||
"service_already_started": "Le service « {service:s} » est déjà démarré",
|
||||
"service_already_stopped": "Le service « {service:s} » est déjà arrêté",
|
||||
"service_cmd_exec_failed": "Impossible d'exécuter la commande « {command:s} »",
|
||||
"service_cmd_exec_failed": "Impossible d’exécuter la commande « {command:s} »",
|
||||
"service_conf_file_backed_up": "Le fichier de configuration « {conf} » a été sauvegardé dans « {backup} »",
|
||||
"service_conf_file_copy_failed": "Impossible de copier le nouveau fichier de configuration « {new} » vers « {conf} »",
|
||||
"service_conf_file_manually_modified": "Le fichier de configuration « {conf} » a été modifié manuellement et ne sera pas mis à jour",
|
||||
|
@ -208,7 +208,7 @@
|
|||
"service_regenconf_dry_pending_applying": "Vérification des configurations en attentes qui pourraient être appliquées pour le service « {service} »…",
|
||||
"service_regenconf_failed": "Impossible de régénérer la configuration pour les services : {services}",
|
||||
"service_regenconf_pending_applying": "Application des configurations en attentes pour le service « {service} »…",
|
||||
"service_remove_failed": "Impossible d'enlever le service « {service:s} »",
|
||||
"service_remove_failed": "Impossible d’enlever le service « {service:s} »",
|
||||
"service_removed": "Le service « {service:s} » a été enlevé",
|
||||
"service_start_failed": "Impossible de démarrer le service « {service:s} »\n\nJournaux récents : {logs:s}",
|
||||
"service_started": "Le service « {service:s} » a été démarré",
|
||||
|
@ -221,38 +221,38 @@
|
|||
"ssowat_conf_generated": "La configuration de SSOwat a été générée",
|
||||
"ssowat_conf_updated": "La configuration de SSOwat a été mise à jour",
|
||||
"system_upgraded": "Le système a été mis à jour",
|
||||
"system_username_exists": "Le nom d'utilisateur existe déjà dans les utilisateurs système",
|
||||
"unbackup_app": "L'application « {app:s} » ne sera pas sauvegardée",
|
||||
"system_username_exists": "Le nom d’utilisateur existe déjà dans les utilisateurs système",
|
||||
"unbackup_app": "L’application « {app:s} » ne sera pas sauvegardée",
|
||||
"unexpected_error": "Une erreur inattendue est survenue",
|
||||
"unit_unknown": "Unité « {unit:s} » inconnue",
|
||||
"unlimit": "Pas de quota",
|
||||
"unrestore_app": "L'application « {app:s} » ne sera pas restaurée",
|
||||
"update_cache_failed": "Impossible de mettre à jour le cache de l'APT",
|
||||
"unrestore_app": "L’application « {app:s} » ne sera pas restaurée",
|
||||
"update_cache_failed": "Impossible de mettre à jour le cache de l’APT",
|
||||
"updating_apt_cache": "Mise à jour de la liste des paquets disponibles...",
|
||||
"upgrade_complete": "Mise à jour terminée",
|
||||
"upgrading_packages": "Mise à jour des paquets...",
|
||||
"upnp_dev_not_found": "Aucun périphérique compatible UPnP n'a été trouvé",
|
||||
"upnp_dev_not_found": "Aucun périphérique compatible UPnP n’a été trouvé",
|
||||
"upnp_disabled": "UPnP a été désactivé",
|
||||
"upnp_enabled": "UPnP a été activé",
|
||||
"upnp_port_open_failed": "Impossible d'ouvrir les ports avec UPnP",
|
||||
"user_created": "L'utilisateur a été créé",
|
||||
"user_creation_failed": "Impossible de créer l'utilisateur",
|
||||
"user_deleted": "L'utilisateur a été supprimé",
|
||||
"user_deletion_failed": "Impossible de supprimer l'utilisateur",
|
||||
"user_home_creation_failed": "Impossible de créer le dossier personnel de l'utilisateur",
|
||||
"user_info_failed": "Impossible de récupérer les informations de l'utilisateur",
|
||||
"upnp_port_open_failed": "Impossible d’ouvrir les ports avec UPnP",
|
||||
"user_created": "L’utilisateur a été créé",
|
||||
"user_creation_failed": "Impossible de créer l’utilisateur",
|
||||
"user_deleted": "L’utilisateur a été supprimé",
|
||||
"user_deletion_failed": "Impossible de supprimer l’utilisateur",
|
||||
"user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur",
|
||||
"user_info_failed": "Impossible de récupérer les informations de l’utilisateur",
|
||||
"user_unknown": "Utilisateur « {user:s} » inconnu",
|
||||
"user_update_failed": "Impossible de modifier l'utilisateur",
|
||||
"user_updated": "L'utilisateur a été modifié",
|
||||
"user_update_failed": "Impossible de modifier l’utilisateur",
|
||||
"user_updated": "L’utilisateur a été modifié",
|
||||
"yunohost_already_installed": "YunoHost est déjà installé",
|
||||
"yunohost_ca_creation_failed": "Impossible de créer l'autorité de certification",
|
||||
"yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification",
|
||||
"yunohost_configured": "YunoHost a été configuré",
|
||||
"yunohost_installing": "Installation de YunoHost...",
|
||||
"yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter « yunohost tools postinstall »",
|
||||
"yunohost_not_installed": "YunoHost n’est pas ou pas correctement installé. Veuillez exécuter « yunohost tools postinstall »",
|
||||
"certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner)",
|
||||
"certmanager_domain_unknown": "Domaine inconnu {domain:s}",
|
||||
"certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force)",
|
||||
"certmanager_certificate_fetching_or_enabling_failed": "Il semble que l'activation du nouveau certificat pour {domain:s} a échoué…",
|
||||
"certmanager_certificate_fetching_or_enabling_failed": "Il semble que l’activation du nouveau certificat pour {domain:s} a échoué…",
|
||||
"certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain:s} n’est pas fourni par Let’s Encrypt. Impossible de le renouveler automatiquement !",
|
||||
"certmanager_attempt_to_renew_valid_cert": "Le certificat pour le domaine {domain:s} est sur le point d’expirer ! Utilisez --force pour contourner",
|
||||
"certmanager_domain_http_not_working": "Il semble que le domaine {domain:s} n’est pas accessible via HTTP. Veuillez vérifier que vos configuration DNS et nginx sont correctes",
|
||||
|
@ -273,15 +273,15 @@
|
|||
"domain_cannot_remove_main": "Impossible de retirer le domaine principal. Définissez un nouveau domaine principal au préalable.",
|
||||
"certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})",
|
||||
"certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})",
|
||||
"mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l'espace disque occupé par la messagerie",
|
||||
"mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l’espace disque occupé par la messagerie",
|
||||
"domains_available": "Domaines disponibles :",
|
||||
"backup_archive_broken_link": "Impossible d'accéder à l'archive de sauvegarde (lien invalide vers {path:s})",
|
||||
"backup_archive_broken_link": "Impossible d’accéder à l’archive 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 préalablement exécuter cert-install pour ce domaine.",
|
||||
"certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être déterminé depuis votre serveur YunoHost. Cela peut arriver si vous avez récemment modifié votre enregistrement DNS. Auquel cas, merci d’attendre quelques heures qu’il se propage. Si le problème persiste, envisager d’ajouter {domain:s} au fichier /etc/hosts. (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces vérifications.)",
|
||||
"certmanager_http_check_timeout": "Expiration du délai lors de la tentative du serveur de se contacter via HTTP en utilisant son adresse IP publique (domaine {domain:s} avec l’IP {ip:s}). Vous rencontrez peut-être un problème d’hairpinning 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 Let’s Encrypt. L’installation/le renouvellement du certificat a été interrompu - veuillez réessayer prochainement.",
|
||||
"appslist_retrieve_bad_format": "Le fichier récupéré pour la liste d’applications {appslist:s} n’est pas valide",
|
||||
"domain_hostname_failed": "Échec de la création d'un nouveau nom d'hôte",
|
||||
"domain_hostname_failed": "Échec de la création d’un nouveau nom d’hôte",
|
||||
"yunohost_ca_creation_success": "L’autorité de certification locale a été créée.",
|
||||
"appslist_name_already_tracked": "Il y a déjà une liste d’applications enregistrée avec le nom {name:s}.",
|
||||
"appslist_url_already_tracked": "Il y a déjà une liste d’applications enregistrée avec l’URL {url:s}.",
|
||||
|
@ -294,7 +294,7 @@
|
|||
"app_change_url_identical_domains": "L’ancien et le nouveau couple domaine/chemin sont identiques pour {domain:s}{path:s}, aucune action.",
|
||||
"app_change_url_no_script": "L’application {app_name:s} ne prend pas encore en charge le changement d’URL. Vous devriez peut-être la mettre à jour.",
|
||||
"app_change_url_success": "L’URL de l’application {app:s} a été changée en {domain:s}{path:s}",
|
||||
"app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante",
|
||||
"app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante\n{apps:s}",
|
||||
"app_already_up_to_date": "{app:s} est déjà à jour",
|
||||
"invalid_url_format": "Format d’URL non valide",
|
||||
"global_settings_bad_choice_for_enum": "La valeur du paramètre {setting:s} est incorrecte. Reçu : {received_type:s}; attendu : {expected_type:s}",
|
||||
|
@ -368,8 +368,8 @@
|
|||
"ask_path": "Chemin",
|
||||
"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 l'app '{app}' par défaut pour le domaine {domain}, déjà utilisé par l'autre app '{other_app}'",
|
||||
"app_upgrade_app_name": "Mise à jour de l'application {app}...",
|
||||
"app_make_default_location_already_used": "Impossible de configurer l’app « {app} » par défaut pour le domaine {domain}, déjà utilisé par l’autre app « {other_app} »",
|
||||
"app_upgrade_app_name": "Mise à jour de l’application {app}...",
|
||||
"backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas, vous avez probablement oublié de monter ou de connecter votre disque / clef USB.",
|
||||
"migrate_tsig_end": "La migration à hmac-sha512 est terminée",
|
||||
"migrate_tsig_failed": "La migration du domaine dyndns {domain} à hmac-sha512 a échoué, annulation des modifications. Erreur : {error_code} - {error}",
|
||||
|
@ -393,7 +393,7 @@
|
|||
"migration_0003_not_jessie": "La distribution Debian actuelle n’est pas Jessie !",
|
||||
"migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.",
|
||||
"migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est ma passé pendant la mise à niveau principale : le système est toujours sur Jessie ?!? Pour investiguer le problème, veuillez regarder {log} 🙁…",
|
||||
"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 d’informations 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\nDe plus, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien 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_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 d’informations 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\nDe plus, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien 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 suivantes, éventuellement problématiques, ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées «working ». En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront 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.",
|
||||
|
@ -416,5 +416,69 @@
|
|||
"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 l’interface web de YunoHost et le système",
|
||||
"service_description_yunohost-firewall": "gère les ports de connexion ouverts et fermés aux services"
|
||||
"service_description_yunohost-firewall": "gère les ports de connexion ouverts et fermés aux services",
|
||||
"experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faîtes.",
|
||||
"log_corrupted_md_file": "Le fichier yaml de metadata associé aux logs est corrompu : {md_file}",
|
||||
"log_category_404": "La catégorie de log « {category} » n’existe pas",
|
||||
"log_link_to_log": "Log complet de cette opération : « <a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\"> {desc} </a> »",
|
||||
"log_help_to_get_log": "Pour voir le log de cette opération « {desc} », utiliser la commande « yunohost log display {name} »",
|
||||
"log_link_to_failed_log": "L’opération « {desc} » a échouée ! Pour avoir de l’aide, merci <a href=\"#/tools/logs/{name}\"> de fournir le log complet de l’opération</a>",
|
||||
"backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge php7, la restauration de vos applications php peut ne pas aboutir (reason: {error:s})",
|
||||
"log_help_to_get_failed_log": "L’opération « {desc} » a échouée ! Pour avoir de l’aide, merci de partager le log de cette opération en utilisant la commande « yunohost log display {name} --share »",
|
||||
"log_does_exists": "Il n’existe pas de log de l’opération ayant pour nom « {log} », utiliser « yunohost log list pour voir tous les fichiers de logs disponibles »",
|
||||
"log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement",
|
||||
"log_app_addaccess": "Ajouter l’accès à « {} »",
|
||||
"log_app_removeaccess": "Enlever l’accès à « {} »",
|
||||
"log_app_clearaccess": "Retirer tous les accès à « {} »",
|
||||
"log_app_fetchlist": "Ajouter une liste d’application",
|
||||
"log_app_removelist": "Enlever une liste d’application",
|
||||
"log_app_change_url": "Changer l’url de l’application « {} »",
|
||||
"log_app_install": "Installer l’application « {} »",
|
||||
"log_app_remove": "Enlever l’application « {} »",
|
||||
"log_app_upgrade": "Mettre à jour l’application « {} »",
|
||||
"log_app_makedefault": "Faire de « {} » l’application par défaut",
|
||||
"log_available_on_yunopaste": "Le log est désormais disponible via {url}",
|
||||
"log_backup_restore_system": "Restaurer le système depuis une sauvegarde",
|
||||
"log_backup_restore_app": "Restaurer « {} » depuis une sauvegarde",
|
||||
"log_remove_on_failed_restore": "Retirer « {} » après la restauration depuis une sauvegarde qui a échouée",
|
||||
"log_remove_on_failed_install": "Enlever « {} » après une installation échouée",
|
||||
"log_domain_add": "Ajouter le domaine « {} » dans la configuration du système",
|
||||
"log_domain_remove": "Enlever le domaine « {} » de la configuration du système",
|
||||
"log_dyndns_subscribe": "Souscrire au sous-domaine « {} » de Yunohost",
|
||||
"log_dyndns_update": "Mettre à jour l’adresse ip associée à votre sous-domaine Yunohost « {} »",
|
||||
"log_letsencrypt_cert_install": "Installer le certificat Let’s encryt sur le domaine « {} »",
|
||||
"log_selfsigned_cert_install": "Installer le certificat auto-signé sur le domaine « {} »",
|
||||
"log_letsencrypt_cert_renew": "Renouveler le certificat Let’s encrypt de « {} »",
|
||||
"log_service_enable": "Activer le service « {} »",
|
||||
"log_service_regen_conf": "Régénérer la configuration système de « {} »",
|
||||
"log_user_create": "Ajouter l’utilisateur « {} »",
|
||||
"log_user_delete": "Enlever l’utilisateur « {} »",
|
||||
"log_user_update": "Mettre à jour les informations de l’utilisateur « {} »",
|
||||
"log_tools_maindomain": "Faire de « {} » le domaine principal",
|
||||
"log_tools_migrations_migrate_forward": "Migrer",
|
||||
"log_tools_migrations_migrate_backward": "Revenir en arrière",
|
||||
"log_tools_postinstall": "Faire la post-installation du serveur Yunohost",
|
||||
"log_tools_upgrade": "Mise à jour des paquets Debian",
|
||||
"log_tools_shutdown": "Eteindre votre serveur",
|
||||
"log_tools_reboot": "Redémarrer votre serveur",
|
||||
"mail_unavailable": "Cette adresse mail est réservée et doit être automatiquement attribuée au tout premier utilisateur",
|
||||
"migration_description_0004_php5_to_php7_pools": "Reconfigurez le pool PHP pour utiliser PHP 7 au lieu de 5",
|
||||
"migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de postgresql 9.4 vers 9.6",
|
||||
"migration_0005_postgresql_94_not_installed": "Postgresql n’a 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": "Il n’y a pas assez d’espace libre de disponible sur {path} pour lancer maintenant la migration :(.",
|
||||
"recommend_to_add_first_user": "La post-installation est terminée, mais YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant « yunohost user create » ou l’interface d’administration.",
|
||||
"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 d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase de chiffrement) et/ou d’utiliser 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 qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase de chiffrement) et/ou d’utiliser différents types de caractères (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 admin et root soient synchronisés. En exécutant cette migration, votre mot de passe root sera remplacé par le mot de passe administrateur.",
|
||||
"migration_0006_done": "Votre mot de passe root a été remplacé par celui de votre adminitrateur.",
|
||||
"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 unique.",
|
||||
"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 caractères majuscules et minuscules",
|
||||
"password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des caractères majuscules, minuscules et spéciaux",
|
||||
"password_too_simple_4": "Le mot de passe doit comporter au moins 12 caractères et contenir des chiffres, des caractères majuscules, minuscules et spéciaux",
|
||||
"root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager sur le mot de passe root !"
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
"service_disabled": "Il servizio '{service:s}' è stato disattivato",
|
||||
"service_remove_failed": "Impossibile rimuovere il servizio '{service:s}'",
|
||||
"service_removed": "Il servizio '{service:s}' è stato rimosso",
|
||||
"service_stop_failed": "Impossibile fermare il servizio '{service:s}'",
|
||||
"service_stop_failed": "Impossibile fermare il servizio '{service:s}'\n\nRegistri di servizio recenti:{logs:s}",
|
||||
"system_username_exists": "il nome utente esiste già negli utenti del sistema",
|
||||
"unrestore_app": "L'applicazione '{app:s}' non verrà ripristinata",
|
||||
"upgrading_packages": "Aggiornamento dei pacchetti...",
|
||||
|
@ -31,11 +31,11 @@
|
|||
"admin_password": "Password dell'amministrazione",
|
||||
"admin_password_change_failed": "Impossibile cambiare la password",
|
||||
"admin_password_changed": "La password dell'amministrazione è stata cambiata",
|
||||
"app_incompatible": "L'app non è compatibile con la tua versione di Yunohost",
|
||||
"app_incompatible": "L'applicazione {app} è incompatibile con la tua versione YunoHost",
|
||||
"app_install_files_invalid": "Non sono validi i file di installazione",
|
||||
"app_location_already_used": "Un'app è già installata in questa posizione",
|
||||
"app_location_install_failed": "Impossibile installare l'applicazione in questa posizione",
|
||||
"app_manifest_invalid": "Manifesto dell'applicazione non valido",
|
||||
"app_location_already_used": "L'applicazione '{app}' è già installata in questo percorso ({path})",
|
||||
"app_location_install_failed": "Impossibile installare l'applicazione in questo percorso perchè andrebbe in conflitto con l'applicazione '{other_app}' già installata in '{other_path}'",
|
||||
"app_manifest_invalid": "Manifesto dell'applicazione non valido: {error}",
|
||||
"app_no_upgrade": "Nessun applicazione da aggiornare",
|
||||
"app_not_correctly_installed": "{app:s} sembra di non essere installata correttamente",
|
||||
"app_not_properly_removed": "{app:s} non è stata correttamente rimossa",
|
||||
|
@ -44,13 +44,13 @@
|
|||
"app_sources_fetch_failed": "Impossibile riportare i file sorgenti",
|
||||
"app_upgrade_failed": "Impossibile aggiornare {app:s}",
|
||||
"app_upgraded": "{app:s} è stata aggiornata",
|
||||
"appslist_fetched": "La lista delle applicazioni è stata recuperata",
|
||||
"appslist_removed": "La lista delle applicazioni è stata rimossa",
|
||||
"app_package_need_update": "Il pacchetto dell'app deve esser aggiornato per seguire le modifiche di Yunohost",
|
||||
"app_requirements_checking": "Controllo dei pacchetti necessari...",
|
||||
"app_requirements_failed": "Impossibile rispondere ai requisiti: {error}",
|
||||
"app_requirements_unmeet": "Non sono soddisfatti i requisiti, il pacchetto {pkgname} ({version}) deve esser {spec}",
|
||||
"appslist_unknown": "Lista di applicazioni sconosciuta",
|
||||
"appslist_fetched": "La lista delle applicazioni {appslist:s} è stata recuperata",
|
||||
"appslist_removed": "La lista delle applicazioni {appslist:s} è stata rimossa",
|
||||
"app_package_need_update": "Il pacchetto dell'applicazione {app} deve essere aggiornato per seguire i cambiamenti di YunoHost",
|
||||
"app_requirements_checking": "Controllo i pacchetti richiesti per {app}...",
|
||||
"app_requirements_failed": "Impossibile soddisfare i requisiti per {app}: {error}",
|
||||
"app_requirements_unmeet": "Requisiti non soddisfatti per {app}, il pacchetto {pkgname} ({version}) deve essere {spec}",
|
||||
"appslist_unknown": "Lista di applicazioni {appslist:s} sconosciuta.",
|
||||
"ask_current_admin_password": "Password attuale dell'amministrazione",
|
||||
"ask_firstname": "Nome",
|
||||
"ask_lastname": "Cognome",
|
||||
|
@ -65,8 +65,8 @@
|
|||
"app_argument_required": "L'argomento '{name:s}' è requisito",
|
||||
"app_id_invalid": "Identificativo dell'applicazione non valido",
|
||||
"app_unsupported_remote_type": "Il tipo remoto usato per l'applicazione non è supportato",
|
||||
"appslist_retrieve_error": "Non è possibile riportare la lista remota delle applicazioni: {error}",
|
||||
"appslist_retrieve_bad_format": "Il file recuperato non è una lista di applicazioni valida",
|
||||
"appslist_retrieve_error": "Impossibile recuperare la lista di applicazioni remote {appslist:s}: {error:s}",
|
||||
"appslist_retrieve_bad_format": "Il file recuperato per la lista di applicazioni {appslist:s} non è valido",
|
||||
"backup_archive_broken_link": "Non è possibile accedere al archivio di backup (link rotto verso {path:s})",
|
||||
"backup_archive_hook_not_exec": "Il hook '{hook:s}' non è stato eseguito in questo backup",
|
||||
"backup_archive_name_unknown": "Archivio di backup locale chiamato '{name:s}' sconosciuto",
|
||||
|
@ -114,7 +114,7 @@
|
|||
"dyndns_no_domain_registered": "Nessuno dominio è stato registrato con DynDNS",
|
||||
"dyndns_registered": "Il dominio DynDNS è stato registrato",
|
||||
"dyndns_registration_failed": "Non è possibile registrare il dominio DynDNS: {error:s}",
|
||||
"dyndns_unavailable": "Il sottodominio DynDNS non è disponibile",
|
||||
"dyndns_unavailable": "Dominio {domain:s} non disponibile.",
|
||||
"executing_command": "Esecuzione del comando '{command:s}'...",
|
||||
"executing_script": "Esecuzione dello script '{script:s}'...",
|
||||
"extracting": "Estrazione...",
|
||||
|
@ -126,7 +126,7 @@
|
|||
"hook_exec_failed": "L'esecuzione dello script è fallita: {path:s}",
|
||||
"hook_exec_not_terminated": "L'esecuzione dello script non è stata terminata: {path:s}",
|
||||
"hook_name_unknown": "Nome di hook '{name:s}' sconosciuto",
|
||||
"installation_complete": "Installazione finita",
|
||||
"installation_complete": "Installazione completata",
|
||||
"installation_failed": "Installazione fallita",
|
||||
"ip6tables_unavailable": "Non puoi giocare con ip6tables qui. O sei in un container o il tuo kernel non lo supporta",
|
||||
"iptables_unavailable": "Non puoi giocare con iptables qui. O sei in un container o il tuo kernel non lo supporta",
|
||||
|
@ -185,7 +185,7 @@
|
|||
"network_check_smtp_ok": "La posta in uscita (SMTP porta 25) non è bloccata",
|
||||
"no_restore_script": "Nessuno script di ripristino trovato per l'applicazone '{app:s}'",
|
||||
"package_unexpected_error": "Un'errore inaspettata si è verificata durante il trattamento del pacchetto '{pkgname}'",
|
||||
"restore_hook_unavailable": "Il hook di ripristino '{hook:s}' non è disponibile sul tuo sistema",
|
||||
"restore_hook_unavailable": "Lo script di ripristino per '{part:s}' non è disponibile per il tuo sistema e non è nemmeno nell'archivio",
|
||||
"restore_nothings_done": "Non è stato ripristinato nulla",
|
||||
"restore_running_app_script": "Esecuzione dello script di ripristino dell'applcicazione '{app:s}'...",
|
||||
"restore_running_hooks": "Esecuzione dei hook di ripristino...",
|
||||
|
@ -203,14 +203,14 @@
|
|||
"service_conf_up_to_date": "La configurazione è già aggiornata per il servizio '{service}'",
|
||||
"service_conf_updated": "La configurazione è stata aggiornata per il servizio '{service}'",
|
||||
"service_conf_would_be_updated": "La configurazione sarebbe stata aggiornata per il servizio '{service}'",
|
||||
"service_disable_failed": "Impossibile disattivare il servizio '{service:s}'",
|
||||
"service_enable_failed": "Impossibile attivare il servizio '{service:s}'",
|
||||
"service_disable_failed": "Impossibile disabilitare il servizio '{service:s}'\n\nRegistri di servizio recenti:{logs:s}",
|
||||
"service_enable_failed": "Impossibile abilitare il servizio '{service:s}'\n\nRegistri di servizio recenti:{logs:s}",
|
||||
"service_enabled": "Il servizio '{service:s}' è stato attivato",
|
||||
"service_no_log": "Nessuno registro da visualizzare per il servizio '{service:s}'",
|
||||
"service_regenconf_dry_pending_applying": "Verificazione della configurazione in attesa che sarebbe stata applicata per il servizio '{service}'...",
|
||||
"service_regenconf_failed": "Impossibile rigenerare la configurazione per il/i servizio/i: {services}",
|
||||
"service_regenconf_pending_applying": "Applicazione della configurazione in attesa per il servizio '{service}'...",
|
||||
"service_start_failed": "Impossibile avviare il servizio '{service:s}'",
|
||||
"service_start_failed": "Impossibile eseguire il servizio '{service:s}'\n\nRegistri di servizio recenti:{logs:s}",
|
||||
"service_started": "Il servizio '{service:s}' è stato avviato",
|
||||
"service_status_failed": "Impossibile determinare lo stato del servizio '{service:s}'",
|
||||
"service_stopped": "Il servizio '{service:s}' è stato fermato",
|
||||
|
@ -250,5 +250,53 @@
|
|||
"certmanager_certificate_fetching_or_enabling_failed": "L'attivazione del nuovo certificato per {domain:s} sembra fallita in qualche modo...",
|
||||
"certmanager_attempt_to_renew_nonLE_cert": "Il certificato per il dominio {domain:s} non è emesso da Let's Encrypt. Impossibile rinnovarlo automaticamente!",
|
||||
"certmanager_attempt_to_renew_valid_cert": "Il certificato per il dominio {domain:s} non è a scadere! Usa --force per ignorare",
|
||||
"certmanager_domain_http_not_working": "Sembra che non sia possibile accedere al dominio {domain:s} attraverso HTTP. Verifica la configurazione del DNS e di nginx"
|
||||
"certmanager_domain_http_not_working": "Sembra che non sia possibile accedere al dominio {domain:s} attraverso HTTP. Verifica la configurazione del DNS e di nginx",
|
||||
"app_already_installed_cant_change_url": "Questa applicazione è già installata. L'URL non può essere cambiato solo da questa funzione. Guarda se `app changeurl` è disponibile.",
|
||||
"app_already_up_to_date": "{app:s} è già aggiornata",
|
||||
"app_change_no_change_url_script": "L'applicazione {app_name:s} non supporta ancora il cambio del proprio URL, potrebbe essere necessario aggiornarla.",
|
||||
"app_change_url_failed_nginx_reload": "Riavvio di nginx fallito. Questo è il risultato di 'nginx -t':\n{nginx_errors:s}",
|
||||
"app_change_url_identical_domains": "Il vecchio ed il nuovo dominio/percorso_url sono identici ('{domain:s}{path:s}'), nessuna operazione necessaria.",
|
||||
"app_change_url_no_script": "L'applicazione '{app_name:s}' non supporta ancora la modifica dell'URL. Forse dovresti aggiornare l'applicazione.",
|
||||
"app_change_url_success": "URL dell'applicazione {app:s} cambiato con successo in {domain:s}{path:s}",
|
||||
"app_make_default_location_already_used": "Impostazione dell'applicazione '{app}' come predefinita del dominio {domain} non riuscita perchè è già stata impostata per l'altra applicazione '{other_app}'",
|
||||
"app_location_unavailable": "Questo URL non è disponibile o va in conflitto con la/le applicazione/i già installata/e:\n{apps:s}",
|
||||
"app_upgrade_app_name": "Aggiornando l'applicazione {app}...",
|
||||
"app_upgrade_some_app_failed": "Impossibile aggiornare alcune applicazioni",
|
||||
"appslist_corrupted_json": "Caricamento della lista delle applicazioni non riuscita. Sembra che {filename:s} sia corrotto.",
|
||||
"appslist_could_not_migrate": "Migrazione della lista delle applicazioni {appslist:s} non riuscita! Impossibile analizzare l'URL... La vecchia operazione pianificata è stata tenuta in {bkp_file:s}.",
|
||||
"appslist_migrating": "Migrando la lista di applicazioni {appslist:s} ...",
|
||||
"appslist_name_already_tracked": "C'è già una lista di applicazioni registrata con il nome {name:s}.",
|
||||
"appslist_url_already_tracked": "C'è già una lista di applicazioni registrata con URL {url:s}.",
|
||||
"ask_path": "Percorso",
|
||||
"backup_abstract_method": "Questo metodo di backup non è ancora stato implementato",
|
||||
"backup_applying_method_borg": "Inviando tutti i file da salvare nel backup nel deposito borg-backup...",
|
||||
"backup_applying_method_copy": "Copiando tutti i files nel backup...",
|
||||
"backup_applying_method_custom": "Chiamando il metodo di backup personalizzato '{method:s}'...",
|
||||
"backup_applying_method_tar": "Creando l'archivio tar del backup...",
|
||||
"backup_archive_mount_failed": "Montaggio dell'archivio del backup non riuscito",
|
||||
"backup_archive_system_part_not_available": "La parte di sistema '{part:s}' non è disponibile in questo backup",
|
||||
"backup_archive_writing_error": "Impossibile aggiungere i file al backup nell'archivio compresso",
|
||||
"backup_ask_for_copying_if_needed": "Alcuni files non possono essere preparati al backup utilizzando il metodo che consente di evitare il consumo temporaneo di spazio nel sistema. Per eseguire il backup, {size:s}MB dovranno essere utilizzati temporaneamente. Sei d'accordo?",
|
||||
"backup_borg_not_implemented": "Il metodo di backup Borg non è ancora stato implementato",
|
||||
"backup_cant_mount_uncompress_archive": "Impossibile montare in modalità sola lettura la cartella di archivio non compressa",
|
||||
"backup_copying_to_organize_the_archive": "Copiando {size:s}MB per organizzare l'archivio",
|
||||
"backup_couldnt_bind": "Impossibile legare {src:s} a {dest:s}.",
|
||||
"backup_csv_addition_failed": "Impossibile aggiungere file del backup nel file CSV",
|
||||
"backup_csv_creation_failed": "Impossibile creare il file CVS richiesto per le future operazioni di ripristino",
|
||||
"backup_custom_backup_error": "Il metodo di backup personalizzato è fallito allo step 'backup'",
|
||||
"backup_custom_mount_error": "Il metodo di backup personalizzato è fallito allo step 'mount'",
|
||||
"backup_custom_need_mount_error": "Il metodo di backup personalizzato è fallito allo step 'need_mount'",
|
||||
"backup_method_borg_finished": "Backup in borg terminato",
|
||||
"backup_method_copy_finished": "Copia di backup terminata",
|
||||
"backup_method_custom_finished": "Metodo di backup personalizzato '{method:s}' terminato",
|
||||
"backup_method_tar_finished": "Archivio tar di backup creato",
|
||||
"backup_no_uncompress_archive_dir": "La cartella di archivio non compressa non esiste",
|
||||
"backup_php5_to_php7_migration_may_fail": "Conversione del tuo archivio per supportare php7 non riuscita, le tue app php potrebbero fallire in fase di ripristino (motivo: {error:s})",
|
||||
"backup_system_part_failed": "Impossibile creare il backup della parte di sistema '{part:s}'",
|
||||
"backup_unable_to_organize_files": "Impossibile organizzare i file nell'archivio con il metodo veloce",
|
||||
"backup_with_no_backup_script_for_app": "L'app {app:s} non ha script di backup. Ignorata.",
|
||||
"backup_with_no_restore_script_for_app": "L'app {app:s} non ha script di ripristino, non sarai in grado di ripristinarla automaticamente dal backup di questa app.",
|
||||
"certmanager_acme_not_configured_for_domain": "Il certificato per il dominio {domain:s} non sembra essere correttamente installato. Per favore esegui cert-install per questo dominio prima.",
|
||||
"certmanager_cannot_read_cert": "Qualcosa è andato storto nel tentativo di aprire il certificato attuale per il dominio {domain:s} (file: {file:s}), motivo: {reason:s}",
|
||||
"certmanager_cert_install_success": "Certificato Let's Encrypt per il dominio {domain:s} installato con successo!"
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
"app_change_url_no_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, benlèu que vos cal la metre a jorn.",
|
||||
"app_make_default_location_already_used": "Impossible de configurar l’aplicacion « {app} » per defaut pel domeni {domain} perque es ja utilizat per l’aplicacion {other_app}",
|
||||
"app_location_install_failed": "Impossible d’installar l’aplicacion a aqueste emplaçament per causa de conflicte amb l’aplicacion {other_app} qu’es ja installada sus {other_path}",
|
||||
"app_location_unavailable": "Aquesta URL es pas disponibla o en conflicte amb una aplicacion existenta",
|
||||
"app_location_unavailable": "Aquesta URL es pas disponibla o en conflicte amb una aplicacion existenta :\n{apps:s}",
|
||||
"appslist_corrupted_json": "Cargament impossible de la lista d’aplicacion. Sembla que {filename:s} siá gastat.",
|
||||
"backup_delete_error": "Impossible de suprimir « {path:s} »",
|
||||
"backup_deleted": "La salvagarda es estada suprimida",
|
||||
|
@ -402,5 +402,69 @@
|
|||
"backup_archive_writing_error": "Impossible d’ajustar los fichièrs a la salvagarda dins l’archiu comprimit",
|
||||
"backup_cant_mount_uncompress_archive": "Impossible de montar en lectura sola lo repertòri de l’archiu descomprimit",
|
||||
"backup_no_uncompress_archive_dir": "Lo repertòri de l’archiu descomprimit existís pas",
|
||||
"pattern_username": "Deu èsser compausat solament de caractèrs alfanumerics en letras minusculas e de tirets basses"
|
||||
"pattern_username": "Deu èsser compausat solament de caractèrs alfanumerics en letras minusculas e de tirets basses",
|
||||
"experimental_feature": "Atencion : aquesta foncionalitat es experimentala e deu pas èsser considerada coma establa, deuriatz pas l’utilizar levat que sapiatz çò que fasètz.",
|
||||
"log_corrupted_md_file": "Lo fichièr yaml de metadonada amb los jornals d’audit es corromput : « {md_file} »",
|
||||
"log_category_404": "La categoria de jornals d’audit « {category} » existís pas",
|
||||
"log_link_to_log": "Jornal complèt d’aquesta operacion : <a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>",
|
||||
"log_help_to_get_log": "Per veire lo jornal d’aquesta operacion « {desc} », utilizatz la comanda « yunohost log display {name} »",
|
||||
"backup_php5_to_php7_migration_may_fail": "Impossible de convertir vòstre archiu per prendre en carga PHP 7, la restauracion de vòstras aplicacions PHP pòt reüssir pas (rason : {error:s})",
|
||||
"log_link_to_failed_log": "L’operacion « {desc} » a pas capitat ! Per obténer d’ajuda, mercés <a href=\"#/tools/logs/{name}\"> de fornir lo jornal complèt de l’operacion</a>",
|
||||
"log_help_to_get_failed_log": "L’operacion « {desc} » a pas reüssit ! Per obténer d’ajuda, mercés de partejar lo jornal d’audit complèt d’aquesta operacion en utilizant la comanda « yunohost log display {name} --share »",
|
||||
"log_does_exists": "I a pas cap de jornal d’audit per l’operacion amb lo nom « {log} », utilizatz « yunohost log list » per veire totes los jornals d’operacion disponibles",
|
||||
"log_operation_unit_unclosed_properly": "L’operacion a pas acabat corrèctament",
|
||||
"log_app_addaccess": "Ajustar l’accès a « {} »",
|
||||
"log_app_removeaccess": "Tirar l’accès a « {} »",
|
||||
"log_app_clearaccess": "Tirar totes los accèsses a « {} »",
|
||||
"log_app_fetchlist": "Ajustar una lista d’aplicacions",
|
||||
"log_app_removelist": "Levar una lista d’aplicacions",
|
||||
"log_app_change_url": "Cambiar l’URL de l’aplicacion « {} »",
|
||||
"log_app_install": "Installar l’aplicacion « {} »",
|
||||
"log_app_remove": "Levar l’aplicacion « {} »",
|
||||
"log_app_upgrade": "Metre a jorn l’aplicacion « {} »",
|
||||
"log_app_makedefault": "Far venir « {} » l’aplicacion per defaut",
|
||||
"log_available_on_yunopaste": "Lo jornal es ara disponible via {url}",
|
||||
"log_backup_restore_system": "Restaurar lo sistèma a partir d’una salvagarda",
|
||||
"log_backup_restore_app": "Restaurar « {} » a partir d’una salvagarda",
|
||||
"log_remove_on_failed_restore": "Levar « {} » aprèp un fracàs de restauracion a partir d’una salvagarda",
|
||||
"log_remove_on_failed_install": "Tirar « {} » aprèp una installacion pas reüssida",
|
||||
"log_domain_add": "Ajustar lo domeni « {} » dins la configuracion sistèma",
|
||||
"log_domain_remove": "Tirar lo domeni « {} » d’a la configuracion sistèma",
|
||||
"log_dyndns_subscribe": "S’abonar al subdomeni YunoHost « {} »",
|
||||
"log_dyndns_update": "Metre a jorn l’adreça IP ligada a vòstre jos-domeni YunoHost « {} »",
|
||||
"log_letsencrypt_cert_install": "Installar lo certificat Let's encrypt sul domeni « {} »",
|
||||
"log_selfsigned_cert_install": "Installar lo certificat auto-signat sul domeni « {} »",
|
||||
"log_letsencrypt_cert_renew": "Renovar lo certificat Let's encrypt de « {} »",
|
||||
"log_service_enable": "Activar lo servici « {} »",
|
||||
"log_service_regen_conf": "Regenerar la configuracion sistèma de « {} »",
|
||||
"log_user_create": "Ajustar l’utilizaire « {} »",
|
||||
"log_user_delete": "Levar l’utilizaire « {} »",
|
||||
"log_user_update": "Metre a jorn las informacions a l’utilizaire « {} »",
|
||||
"log_tools_maindomain": "Far venir « {} » lo domeni màger",
|
||||
"log_tools_migrations_migrate_forward": "Migrar",
|
||||
"log_tools_migrations_migrate_backward": "Tornar en arrièr",
|
||||
"log_tools_postinstall": "Realizar la post installacion del servidor YunoHost",
|
||||
"log_tools_upgrade": "Mesa a jorn dels paquets Debian",
|
||||
"log_tools_shutdown": "Atudar lo servidor",
|
||||
"log_tools_reboot": "Reaviar lo servidor",
|
||||
"mail_unavailable": "Aquesta adreça electronica es reservada e deu èsser automaticament atribuida al tot bèl just primièr utilizaire",
|
||||
"migration_description_0004_php5_to_php7_pools": "Tornar configurar lo pools PHP per utilizar PHP 7 allòc del 5",
|
||||
"migration_description_0005_postgresql_9p4_to_9p6": "Migracion de las basas de donadas de postgresql 9.4 cap a 9.6",
|
||||
"migration_0005_postgresql_94_not_installed": "Postgresql es pas installat sul sistèma. Pas res de far !",
|
||||
"migration_0005_postgresql_96_not_installed": "Avèm trobat que Postgresql 9.4 es installat, mas cap de version de Postgresql 9.6 pas trobada !? Quicòm d’estranh a degut arribar a vòstre sistèma :( …",
|
||||
"migration_0005_not_enough_space": "I a pas pro d’espaci disponible sus {path} per lançar la migracion d’aquela passa :(.",
|
||||
"recommend_to_add_first_user": "La post installacion es acabada, mas YunoHost fa besonh d’almens un utilizaire per foncionar coma cal. Vos cal n’ajustar un en utilizant la comanda « yunohost user create » o ben l’interfàcia d’administracion.",
|
||||
"service_description_php7.0-fpm": "executa d’aplicacions escrichas en PHP amb nginx",
|
||||
"users_available": "Lista dels utilizaires disponibles :",
|
||||
"good_practices_about_admin_password": "Sètz per definir un nòu senhal per l’administracion. Lo senhal deu almens conténer 8 caractèrs - encara que siá de bon far d’utilizar un senhal mai long qu’aquò (ex. una passafrasa) e/o d’utilizar mantun tipes de caractèrs (majuscula, minuscula, nombre e caractèrs especials).",
|
||||
"good_practices_about_user_password": "Sètz per definir un nòu senhal d’utilizaire. Lo senhal deu almens conténer 8 caractèrs - encara que siá de bon far d’utilizar un senhal mai long qu’aquò (ex. una passafrasa) e/o d’utilizar mantun tipes de caractèrs (majuscula, minuscula, nombre e caractèrs especials).",
|
||||
"migration_description_0006_sync_admin_and_root_passwords": "Sincronizar los senhals admin e root",
|
||||
"migration_0006_disclaimer": "Ara YunoHost s’espèra que los senhals admin e root sián sincronizats. En lançant aquesta migracion, vòstre senhal root serà remplaçat pel senhal admin.",
|
||||
"migration_0006_done": "Lo senhal root es estat remplaçat pel senhal admin.",
|
||||
"password_listed": "Aqueste senhal fa part dels senhals mai utilizats del monde. Volgatz ben ne causir un mai unic.",
|
||||
"password_too_simple_1": "Lo senhal deu conténer almens 8 caractèrs",
|
||||
"password_too_simple_2": "Lo senhal deu conténer almens 8 caractèrs amb de nombres, de majusculas e de minusculas",
|
||||
"password_too_simple_3": "Lo senhal deu conténer almens 8 caractèrs amb de nombres, de majusculas, de minusculas e de caractèrs especials",
|
||||
"password_too_simple_4": "Lo senhal deu conténer almens 12 caractèrs amb de nombres, de majusculas, de minusculas e de caractèrs especials",
|
||||
"root_password_desynchronized": "Lo senhal de l’administrator es estat cambiat, mas YunoHost a pas pogut l’espandir al senhal root !"
|
||||
}
|
||||
|
|
|
@ -192,5 +192,6 @@
|
|||
"backup_ask_for_copying_if_needed": "Alguns arquivos não consiguiram ser preparados para backup utilizando o metodo que não gasta espaço de disco temporariamente. Para realizar o backup {size:s}MB precisam ser usados temporariamente. Você concorda?",
|
||||
"backup_borg_not_implemented": "O método de backup Borg ainda não foi implementado.",
|
||||
"backup_cant_mount_uncompress_archive": "Não foi possível montar em modo leitura o diretorio de arquivos não comprimido",
|
||||
"backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar o arquivo"
|
||||
"backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar o arquivo",
|
||||
"app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain:s}{path:s}'), nada para fazer."
|
||||
}
|
||||
|
|
|
@ -30,15 +30,15 @@ import yaml
|
|||
import time
|
||||
import re
|
||||
import urlparse
|
||||
import errno
|
||||
import subprocess
|
||||
import glob
|
||||
import pwd
|
||||
import grp
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
|
||||
from moulinette import msignals, m18n, msettings
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.filesystem import read_json
|
||||
|
||||
|
@ -80,6 +80,12 @@ def app_listlists():
|
|||
# Get the list
|
||||
appslist_list = _read_appslist_list()
|
||||
|
||||
# Convert 'lastUpdate' timestamp to datetime
|
||||
for name, infos in appslist_list.items():
|
||||
if infos["lastUpdate"] is None:
|
||||
infos["lastUpdate"] = 0
|
||||
infos["lastUpdate"] = datetime.utcfromtimestamp(infos["lastUpdate"])
|
||||
|
||||
return appslist_list
|
||||
|
||||
|
||||
|
@ -118,14 +124,12 @@ def app_fetchlist(url=None, name=None):
|
|||
appslists_to_be_fetched = [name]
|
||||
operation_logger.success()
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('custom_appslist_name_required'))
|
||||
raise YunohostError('custom_appslist_name_required')
|
||||
|
||||
# If a name is given, look for an appslist with that name and fetch it
|
||||
elif name is not None:
|
||||
if name not in appslists.keys():
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('appslist_unknown', appslist=name))
|
||||
raise YunohostError('appslist_unknown', appslist=name)
|
||||
else:
|
||||
appslists_to_be_fetched = [name]
|
||||
|
||||
|
@ -133,7 +137,7 @@ def app_fetchlist(url=None, name=None):
|
|||
else:
|
||||
appslists_to_be_fetched = appslists.keys()
|
||||
|
||||
import requests # lazy loading this module for performance reasons
|
||||
import requests # lazy loading this module for performance reasons
|
||||
# Fetch all appslists to be fetched
|
||||
for name in appslists_to_be_fetched:
|
||||
|
||||
|
@ -168,7 +172,7 @@ def app_fetchlist(url=None, name=None):
|
|||
appslist = appslist_request.text
|
||||
try:
|
||||
json.loads(appslist)
|
||||
except ValueError, e:
|
||||
except ValueError as e:
|
||||
logger.error(m18n.n('appslist_retrieve_bad_format',
|
||||
appslist=name))
|
||||
continue
|
||||
|
@ -179,9 +183,7 @@ def app_fetchlist(url=None, name=None):
|
|||
with open(list_file, "w") as f:
|
||||
f.write(appslist)
|
||||
except Exception as e:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
"Error while writing appslist %s: %s" %
|
||||
(name, str(e)))
|
||||
raise YunohostError("Error while writing appslist %s: %s" % (name, str(e)), raw_msg=True)
|
||||
|
||||
now = int(time.time())
|
||||
appslists[name]["lastUpdate"] = now
|
||||
|
@ -205,7 +207,7 @@ def app_removelist(operation_logger, name):
|
|||
|
||||
# Make sure we know this appslist
|
||||
if name not in appslists.keys():
|
||||
raise MoulinetteError(errno.ENOENT, m18n.n('appslist_unknown', appslist=name))
|
||||
raise YunohostError('appslist_unknown', appslist=name)
|
||||
|
||||
operation_logger.start()
|
||||
|
||||
|
@ -336,8 +338,7 @@ def app_info(app, show_status=False, raw=False):
|
|||
|
||||
"""
|
||||
if not _is_installed(app):
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_not_installed', app=app))
|
||||
raise YunohostError('app_not_installed', app=app)
|
||||
|
||||
app_setting_path = APPS_SETTING_PATH + app
|
||||
|
||||
|
@ -346,7 +347,8 @@ def app_info(app, show_status=False, raw=False):
|
|||
ret['settings'] = _get_app_settings(app)
|
||||
|
||||
# Determine upgradability
|
||||
local_update_time = ret['settings'].get('update_time', ret['settings']['install_time'])
|
||||
# In case there is neither update_time nor install_time, we assume the app can/has to be upgraded
|
||||
local_update_time = ret['settings'].get('update_time', ret['settings'].get('install_time', 0))
|
||||
|
||||
if 'lastUpdate' not in ret or 'git' not in ret:
|
||||
upgradable = "url_required"
|
||||
|
@ -394,8 +396,7 @@ def app_map(app=None, raw=False, user=None):
|
|||
|
||||
if app is not None:
|
||||
if not _is_installed(app):
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_not_installed', app=app))
|
||||
raise YunohostError('app_not_installed', app=app)
|
||||
apps = [app, ]
|
||||
else:
|
||||
apps = os.listdir(APPS_SETTING_PATH)
|
||||
|
@ -447,11 +448,10 @@ def app_change_url(operation_logger, auth, app, domain, path):
|
|||
|
||||
installed = _is_installed(app)
|
||||
if not installed:
|
||||
raise MoulinetteError(errno.ENOPKG,
|
||||
m18n.n('app_not_installed', app=app))
|
||||
raise YunohostError('app_not_installed', app=app)
|
||||
|
||||
if not os.path.exists(os.path.join(APPS_SETTING_PATH, app, "scripts", "change_url")):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n("app_change_no_change_url_script", app_name=app))
|
||||
raise YunohostError("app_change_no_change_url_script", app_name=app)
|
||||
|
||||
old_domain = app_setting(app, "domain")
|
||||
old_path = app_setting(app, "path")
|
||||
|
@ -463,7 +463,7 @@ def app_change_url(operation_logger, auth, app, domain, path):
|
|||
path = normalize_url_path(path)
|
||||
|
||||
if (domain, path) == (old_domain, old_path):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n("app_change_url_identical_domains", domain=domain, path=path))
|
||||
raise YunohostError("app_change_url_identical_domains", domain=domain, path=path)
|
||||
|
||||
# WARNING / FIXME : checkurl will modify the settings
|
||||
# (this is a non intuitive behavior that should be changed)
|
||||
|
@ -539,10 +539,10 @@ def app_change_url(operation_logger, auth, app, domain, path):
|
|||
stderr=subprocess.STDOUT,
|
||||
shell=True).rstrip()
|
||||
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n("app_change_url_failed_nginx_reload", nginx_errors=nginx_errors))
|
||||
raise YunohostError("app_change_url_failed_nginx_reload", nginx_errors=nginx_errors)
|
||||
|
||||
logger.success(m18n.n("app_change_url_success",
|
||||
app=app, domain=domain, path=path))
|
||||
app=app, domain=domain, path=path))
|
||||
|
||||
hook_callback('post_app_change_url', args=args_list, env=env_dict)
|
||||
|
||||
|
@ -564,8 +564,8 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
|||
|
||||
try:
|
||||
app_list()
|
||||
except MoulinetteError:
|
||||
raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade'))
|
||||
except YunohostError:
|
||||
raise YunohostError('app_no_upgrade')
|
||||
|
||||
upgraded_apps = []
|
||||
|
||||
|
@ -585,8 +585,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
|||
logger.info(m18n.n('app_upgrade_app_name', app=app_instance_name))
|
||||
installed = _is_installed(app_instance_name)
|
||||
if not installed:
|
||||
raise MoulinetteError(errno.ENOPKG,
|
||||
m18n.n('app_not_installed', app=app_instance_name))
|
||||
raise YunohostError('app_not_installed', app=app_instance_name)
|
||||
|
||||
if app_instance_name in upgraded_apps:
|
||||
continue
|
||||
|
@ -676,7 +675,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
|||
operation_logger.success()
|
||||
|
||||
if not upgraded_apps:
|
||||
raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade'))
|
||||
raise YunohostError('app_no_upgrade')
|
||||
|
||||
app_ssowatconf(auth)
|
||||
|
||||
|
@ -688,7 +687,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
|||
|
||||
|
||||
@is_unit_operation()
|
||||
def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on_failure=False):
|
||||
def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on_failure=False, force=False):
|
||||
"""
|
||||
Install apps
|
||||
|
||||
|
@ -697,12 +696,11 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
|
|||
label -- Custom name for the app
|
||||
args -- Serialize arguments for app installation
|
||||
no_remove_on_failure -- Debug option to avoid removing the app on a failed installation
|
||||
|
||||
force -- Do not ask for confirmation when installing experimental / low-quality apps
|
||||
"""
|
||||
from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback
|
||||
from yunohost.log import OperationLogger
|
||||
|
||||
|
||||
# Fetch or extract sources
|
||||
try:
|
||||
os.listdir(INSTALL_TMP)
|
||||
|
@ -717,17 +715,46 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
|
|||
},
|
||||
}
|
||||
|
||||
if app in app_list(raw=True) or ('@' in app) or ('http://' in app) or ('https://' in app):
|
||||
def confirm_install(confirm):
|
||||
|
||||
# Ignore if there's nothing for confirm (good quality app), if --force is used
|
||||
# or if request on the API (confirm already implemented on the API side)
|
||||
if confirm is None or force or msettings.get('interface') == 'api':
|
||||
return
|
||||
|
||||
answer = msignals.prompt(m18n.n('confirm_app_install_' + confirm,
|
||||
answers='Y/N'))
|
||||
if answer.upper() != "Y":
|
||||
raise YunohostError("aborting")
|
||||
|
||||
|
||||
raw_app_list = app_list(raw=True)
|
||||
if app in raw_app_list or ('@' in app) or ('http://' in app) or ('https://' in app):
|
||||
if app in raw_app_list:
|
||||
state = raw_app_list[app].get("state", "notworking")
|
||||
level = raw_app_list[app].get("level", None)
|
||||
confirm = "danger"
|
||||
if state in ["working", "validated"]:
|
||||
if isinstance(level, int) and level >= 3:
|
||||
confirm = None
|
||||
elif isinstance(level, int) and level > 0:
|
||||
confirm = "warning"
|
||||
else:
|
||||
confirm = "thirdparty"
|
||||
|
||||
confirm_install(confirm)
|
||||
|
||||
manifest, extracted_app_folder = _fetch_app_from_git(app)
|
||||
elif os.path.exists(app):
|
||||
confirm_install("thirdparty")
|
||||
manifest, extracted_app_folder = _extract_app_from_file(app)
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('app_unknown'))
|
||||
raise YunohostError('app_unknown')
|
||||
status['remote'] = manifest.get('remote', {})
|
||||
|
||||
# Check ID
|
||||
if 'id' not in manifest or '__' in manifest['id']:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('app_id_invalid'))
|
||||
raise YunohostError('app_id_invalid')
|
||||
|
||||
app_id = manifest['id']
|
||||
|
||||
|
@ -738,8 +765,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
|
|||
instance_number = _installed_instance_number(app_id, last=True) + 1
|
||||
if instance_number > 1:
|
||||
if 'multi_instance' not in manifest or not is_true(manifest['multi_instance']):
|
||||
raise MoulinetteError(errno.EEXIST,
|
||||
m18n.n('app_already_installed', app=app_id))
|
||||
raise YunohostError('app_already_installed', app=app_id)
|
||||
|
||||
# Change app_id to the forked app id
|
||||
app_instance_name = app_id + '__' + str(instance_number)
|
||||
|
@ -760,7 +786,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
|
|||
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(instance_number)
|
||||
|
||||
# Start register change on system
|
||||
operation_logger.extra.update({'env':env_dict})
|
||||
operation_logger.extra.update({'env': env_dict})
|
||||
operation_logger.related_to = [s for s in operation_logger.related_to if s[0] != "app"]
|
||||
operation_logger.related_to.append(("app", app_id))
|
||||
operation_logger.start()
|
||||
|
@ -818,8 +844,8 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
|
|||
|
||||
# Execute remove script
|
||||
operation_logger_remove = OperationLogger('remove_on_failed_install',
|
||||
[('app', app_instance_name)],
|
||||
env=env_dict_remove)
|
||||
[('app', app_instance_name)],
|
||||
env=env_dict_remove)
|
||||
operation_logger_remove.start()
|
||||
|
||||
remove_retcode = hook_exec(
|
||||
|
@ -842,9 +868,9 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
|
|||
|
||||
if install_retcode == -1:
|
||||
msg = m18n.n('operation_interrupted') + " " + error_msg
|
||||
raise MoulinetteError(errno.EINTR, msg)
|
||||
raise YunohostError(msg, raw_msg=True)
|
||||
msg = error_msg
|
||||
raise MoulinetteError(errno.EIO, msg)
|
||||
raise YunohostError(msg, raw_msg=True)
|
||||
|
||||
# Clean hooks and add new ones
|
||||
hook_remove(app_instance_name)
|
||||
|
@ -880,8 +906,7 @@ def app_remove(operation_logger, auth, app):
|
|||
"""
|
||||
from yunohost.hook import hook_exec, hook_remove, hook_callback
|
||||
if not _is_installed(app):
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_not_installed', app=app))
|
||||
raise YunohostError('app_not_installed', app=app)
|
||||
|
||||
operation_logger.start()
|
||||
|
||||
|
@ -947,7 +972,6 @@ def app_addaccess(auth, apps, users=[]):
|
|||
|
||||
for app in apps:
|
||||
|
||||
|
||||
app_settings = _get_app_settings(app)
|
||||
if not app_settings:
|
||||
continue
|
||||
|
@ -960,18 +984,18 @@ def app_addaccess(auth, apps, users=[]):
|
|||
|
||||
# Start register change on system
|
||||
related_to = [('app', app)]
|
||||
operation_logger= OperationLogger('app_addaccess', related_to)
|
||||
operation_logger = OperationLogger('app_addaccess', related_to)
|
||||
operation_logger.start()
|
||||
|
||||
allowed_users = set()
|
||||
if 'allowed_users' in app_settings:
|
||||
if 'allowed_users' in app_settings and app_settings['allowed_users']:
|
||||
allowed_users = set(app_settings['allowed_users'].split(','))
|
||||
|
||||
for allowed_user in users:
|
||||
if allowed_user not in allowed_users:
|
||||
try:
|
||||
user_info(auth, allowed_user)
|
||||
except MoulinetteError:
|
||||
except YunohostError:
|
||||
logger.warning(m18n.n('user_unknown', user=allowed_user))
|
||||
continue
|
||||
allowed_users.add(allowed_user)
|
||||
|
@ -1023,7 +1047,7 @@ def app_removeaccess(auth, apps, users=[]):
|
|||
|
||||
# Start register change on system
|
||||
related_to = [('app', app)]
|
||||
operation_logger= OperationLogger('app_removeaccess', related_to)
|
||||
operation_logger = OperationLogger('app_removeaccess', related_to)
|
||||
operation_logger.start()
|
||||
|
||||
if remove_all:
|
||||
|
@ -1035,9 +1059,9 @@ def app_removeaccess(auth, apps, users=[]):
|
|||
else:
|
||||
for allowed_user in user_list(auth)['users'].keys():
|
||||
if allowed_user not in users:
|
||||
allowed_users.append(allowed_user)
|
||||
allowed_users.add(allowed_user)
|
||||
|
||||
operation_logger.related_to += [ ('user', x) for x in allowed_users ]
|
||||
operation_logger.related_to += [('user', x) for x in allowed_users]
|
||||
operation_logger.flush()
|
||||
new_users = ','.join(allowed_users)
|
||||
app_setting(app, 'allowed_users', new_users)
|
||||
|
@ -1072,7 +1096,7 @@ def app_clearaccess(auth, apps):
|
|||
|
||||
# Start register change on system
|
||||
related_to = [('app', app)]
|
||||
operation_logger= OperationLogger('app_clearaccess', related_to)
|
||||
operation_logger = OperationLogger('app_clearaccess', related_to)
|
||||
operation_logger.start()
|
||||
|
||||
if 'mode' in app_settings:
|
||||
|
@ -1129,23 +1153,19 @@ def app_makedefault(operation_logger, auth, app, domain=None):
|
|||
|
||||
if domain is None:
|
||||
domain = app_domain
|
||||
operation_logger.related_to.append(('domain',domain))
|
||||
operation_logger.related_to.append(('domain', domain))
|
||||
elif domain not in domain_list(auth)['domains']:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
|
||||
raise YunohostError('domain_unknown')
|
||||
|
||||
operation_logger.start()
|
||||
if '/' in app_map(raw=True)[domain]:
|
||||
raise MoulinetteError(errno.EEXIST,
|
||||
m18n.n('app_make_default_location_already_used',
|
||||
app=app, domain=app_domain,
|
||||
other_app=app_map(raw=True)[domain]["/"]["id"]))
|
||||
raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain, other_app=app_map(raw=True)[domain]["/"]["id"])
|
||||
|
||||
try:
|
||||
with open('/etc/ssowat/conf.json.persistent') as json_conf:
|
||||
ssowat_conf = json.loads(str(json_conf.read()))
|
||||
except ValueError as e:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('ssowat_persistent_conf_read_error', error=e.strerror))
|
||||
raise YunohostError('ssowat_persistent_conf_read_error', error=e.strerror)
|
||||
except IOError:
|
||||
ssowat_conf = {}
|
||||
|
||||
|
@ -1158,8 +1178,7 @@ def app_makedefault(operation_logger, auth, app, domain=None):
|
|||
with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
|
||||
json.dump(ssowat_conf, f, sort_keys=True, indent=4)
|
||||
except IOError as e:
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('ssowat_persistent_conf_write_error', error=e.strerror))
|
||||
raise YunohostError('ssowat_persistent_conf_write_error', error=e.strerror)
|
||||
|
||||
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
|
||||
|
||||
|
@ -1211,8 +1230,7 @@ def app_checkport(port):
|
|||
if tools_port_available(port):
|
||||
logger.success(m18n.n('port_available', port=int(port)))
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('port_unavailable', port=int(port)))
|
||||
raise YunohostError('port_unavailable', port=int(port))
|
||||
|
||||
|
||||
def app_register_url(auth, app, domain, path):
|
||||
|
@ -1227,7 +1245,7 @@ def app_register_url(auth, app, domain, path):
|
|||
|
||||
# This line can't be moved on top of file, otherwise it creates an infinite
|
||||
# loop of import with tools.py...
|
||||
from domain import _get_conflicting_apps, _normalize_domain_path
|
||||
from .domain import _get_conflicting_apps, _normalize_domain_path
|
||||
|
||||
domain, path = _normalize_domain_path(domain, path)
|
||||
|
||||
|
@ -1239,8 +1257,7 @@ def app_register_url(auth, app, domain, path):
|
|||
if installed:
|
||||
settings = _get_app_settings(app)
|
||||
if "path" in settings.keys() and "domain" in settings.keys():
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_already_installed_cant_change_url'))
|
||||
raise YunohostError('app_already_installed_cant_change_url')
|
||||
|
||||
# Check the url is available
|
||||
conflicts = _get_conflicting_apps(auth, domain, path)
|
||||
|
@ -1254,7 +1271,7 @@ def app_register_url(auth, app, domain, path):
|
|||
app_label=app_label,
|
||||
))
|
||||
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('app_location_unavailable', apps="\n".join(apps)))
|
||||
raise YunohostError('app_location_unavailable', apps="\n".join(apps))
|
||||
|
||||
app_setting(app, 'domain', value=domain)
|
||||
app_setting(app, 'path', value=path)
|
||||
|
@ -1292,7 +1309,7 @@ def app_checkurl(auth, url, app=None):
|
|||
apps_map = app_map(raw=True)
|
||||
|
||||
if domain not in domain_list(auth)['domains']:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
|
||||
raise YunohostError('domain_unknown')
|
||||
|
||||
if domain in apps_map:
|
||||
# Loop through apps
|
||||
|
@ -1302,14 +1319,10 @@ def app_checkurl(auth, url, app=None):
|
|||
installed = True
|
||||
continue
|
||||
if path == p:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_location_already_used',
|
||||
app=a["id"], path=path))
|
||||
raise YunohostError('app_location_already_used', app=a["id"], path=path)
|
||||
# can't install "/a/b/" if "/a/" exists
|
||||
elif path.startswith(p) or p.startswith(path):
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('app_location_install_failed',
|
||||
other_path=p, other_app=a['id']))
|
||||
raise YunohostError('app_location_install_failed', other_path=p, other_app=a['id'])
|
||||
|
||||
if app is not None and not installed:
|
||||
app_setting(app, 'domain', value=domain)
|
||||
|
@ -1341,10 +1354,10 @@ def app_initdb(user, password=None, db=None, sql=None):
|
|||
mysql_root_pwd = open('/etc/yunohost/mysql').read().rstrip()
|
||||
mysql_command = 'mysql -u root -p%s -e "CREATE DATABASE %s ; GRANT ALL PRIVILEGES ON %s.* TO \'%s\'@localhost IDENTIFIED BY \'%s\';"' % (mysql_root_pwd, db, db, user, password)
|
||||
if os.system(mysql_command) != 0:
|
||||
raise MoulinetteError(errno.EIO, m18n.n('mysql_db_creation_failed'))
|
||||
raise YunohostError('mysql_db_creation_failed')
|
||||
if sql is not None:
|
||||
if os.system('mysql -u %s -p%s %s < %s' % (user, password, db, sql)) != 0:
|
||||
raise MoulinetteError(errno.EIO, m18n.n('mysql_db_init_failed'))
|
||||
raise YunohostError('mysql_db_init_failed')
|
||||
|
||||
if return_pwd:
|
||||
return password
|
||||
|
@ -1450,8 +1463,7 @@ def app_ssowatconf(auth):
|
|||
def app_change_label(auth, app, new_label):
|
||||
installed = _is_installed(app)
|
||||
if not installed:
|
||||
raise MoulinetteError(errno.ENOPKG,
|
||||
m18n.n('app_not_installed', app=app))
|
||||
raise YunohostError('app_not_installed', app=app)
|
||||
|
||||
app_setting(app, "label", value=new_label)
|
||||
|
||||
|
@ -1487,7 +1499,7 @@ def app_action_run(app, action, args=None):
|
|||
actions = {x["id"]: x for x in actions}
|
||||
|
||||
if action not in actions:
|
||||
raise MoulinetteError(errno.EINVAL, "action '%s' not available for app '%s', available actions are: %s" % (action, app, ", ".join(actions.keys())))
|
||||
raise YunohostError("action '%s' not available for app '%s', available actions are: %s" % (action, app, ", ".join(actions.keys())), raw_msg=True)
|
||||
|
||||
action_declaration = actions[action]
|
||||
|
||||
|
@ -1525,7 +1537,7 @@ def app_action_run(app, action, args=None):
|
|||
)
|
||||
|
||||
if retcode not in action_declaration.get("accepted_return_codes", [0]):
|
||||
raise MoulinetteError(retcode, "Error while executing action '%s' of app '%s': return code %s" % (action, app, retcode))
|
||||
raise YunohostError("Error while executing action '%s' of app '%s': return code %s" % (action, app, retcode), raw_msg=True)
|
||||
|
||||
os.remove(path)
|
||||
|
||||
|
@ -1584,10 +1596,10 @@ def app_config_show_panel(app):
|
|||
parsed_values[key] = value
|
||||
|
||||
return_code = hook_exec(config_script,
|
||||
args=["show"],
|
||||
env=env,
|
||||
stdout_callback=parse_stdout,
|
||||
)
|
||||
args=["show"],
|
||||
env=env,
|
||||
stdout_callback=parse_stdout,
|
||||
)
|
||||
|
||||
if return_code != 0:
|
||||
raise Exception("script/config show return value code: %s (considered as an error)", return_code)
|
||||
|
@ -1632,8 +1644,7 @@ def app_config_apply(app, args):
|
|||
|
||||
installed = _is_installed(app)
|
||||
if not installed:
|
||||
raise MoulinetteError(errno.ENOPKG,
|
||||
m18n.n('app_not_installed', app=app))
|
||||
raise YunohostError('app_not_installed', app=app)
|
||||
|
||||
config_panel = os.path.join(APPS_SETTING_PATH, app, 'config_panel.json')
|
||||
config_script = os.path.join(APPS_SETTING_PATH, app, 'scripts', 'config')
|
||||
|
@ -1672,9 +1683,9 @@ def app_config_apply(app, args):
|
|||
logger.warning("Ignore key '%s' from arguments because it is not in the config", key)
|
||||
|
||||
return_code = hook_exec(config_script,
|
||||
args=["apply"],
|
||||
env=env,
|
||||
)
|
||||
args=["apply"],
|
||||
env=env,
|
||||
)
|
||||
|
||||
if return_code != 0:
|
||||
raise Exception("'script/config apply' return value code: %s (considered as an error)", return_code)
|
||||
|
@ -1691,8 +1702,7 @@ def _get_app_settings(app_id):
|
|||
|
||||
"""
|
||||
if not _is_installed(app_id):
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_not_installed', app=app_id))
|
||||
raise YunohostError('app_not_installed', app=app_id)
|
||||
try:
|
||||
with open(os.path.join(
|
||||
APPS_SETTING_PATH, app_id, 'settings.yml')) as f:
|
||||
|
@ -1730,7 +1740,7 @@ def _get_app_status(app_id, format_date=False):
|
|||
"""
|
||||
app_setting_path = APPS_SETTING_PATH + app_id
|
||||
if not os.path.isdir(app_setting_path):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('app_unknown'))
|
||||
raise YunohostError('app_unknown')
|
||||
status = {}
|
||||
|
||||
try:
|
||||
|
@ -1754,8 +1764,7 @@ def _get_app_status(app_id, format_date=False):
|
|||
if not v:
|
||||
status[f] = '-'
|
||||
else:
|
||||
status[f] = time.strftime(m18n.n('format_datetime_short'),
|
||||
time.gmtime(v))
|
||||
status[f] = datetime.utcfromtimestamp(v)
|
||||
return status
|
||||
|
||||
|
||||
|
@ -1796,7 +1805,7 @@ def _extract_app_from_file(path, remove=False):
|
|||
extract_result = 1
|
||||
|
||||
if extract_result != 0:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('app_extraction_failed'))
|
||||
raise YunohostError('app_extraction_failed')
|
||||
|
||||
try:
|
||||
extracted_app_folder = APP_TMP_FOLDER
|
||||
|
@ -1807,10 +1816,9 @@ def _extract_app_from_file(path, remove=False):
|
|||
manifest = json.loads(str(json_manifest.read()))
|
||||
manifest['lastUpdate'] = int(time.time())
|
||||
except IOError:
|
||||
raise MoulinetteError(errno.EIO, m18n.n('app_install_files_invalid'))
|
||||
raise YunohostError('app_install_files_invalid')
|
||||
except ValueError as e:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_manifest_invalid', error=e.strerror))
|
||||
raise YunohostError('app_manifest_invalid', error=e.strerror)
|
||||
|
||||
logger.debug(m18n.n('done'))
|
||||
|
||||
|
@ -1878,8 +1886,7 @@ def _fetch_app_from_git(app):
|
|||
'wget', '-qO', app_tmp_archive, tarball_url])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception('unable to download %s', tarball_url)
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('app_sources_fetch_failed'))
|
||||
raise YunohostError('app_sources_fetch_failed')
|
||||
else:
|
||||
manifest, extracted_app_folder = _extract_app_from_file(
|
||||
app_tmp_archive, remove=True)
|
||||
|
@ -1902,11 +1909,9 @@ def _fetch_app_from_git(app):
|
|||
with open(extracted_app_folder + '/manifest.json') as f:
|
||||
manifest = json.loads(str(f.read()))
|
||||
except subprocess.CalledProcessError:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('app_sources_fetch_failed'))
|
||||
raise YunohostError('app_sources_fetch_failed')
|
||||
except ValueError as e:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('app_manifest_invalid', error=e.strerror))
|
||||
raise YunohostError('app_manifest_invalid', error=e.strerror)
|
||||
else:
|
||||
logger.debug(m18n.n('done'))
|
||||
|
||||
|
@ -1926,11 +1931,10 @@ def _fetch_app_from_git(app):
|
|||
app_info['manifest']['lastUpdate'] = app_info['lastUpdate']
|
||||
manifest = app_info['manifest']
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('app_unknown'))
|
||||
raise YunohostError('app_unknown')
|
||||
|
||||
if 'git' not in app_info:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_unsupported_remote_type'))
|
||||
raise YunohostError('app_unsupported_remote_type')
|
||||
url = app_info['git']['url']
|
||||
|
||||
if 'github.com' in url:
|
||||
|
@ -1942,8 +1946,7 @@ def _fetch_app_from_git(app):
|
|||
'wget', '-qO', app_tmp_archive, tarball_url])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception('unable to download %s', tarball_url)
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('app_sources_fetch_failed'))
|
||||
raise YunohostError('app_sources_fetch_failed')
|
||||
else:
|
||||
manifest, extracted_app_folder = _extract_app_from_file(
|
||||
app_tmp_archive, remove=True)
|
||||
|
@ -1959,11 +1962,9 @@ def _fetch_app_from_git(app):
|
|||
with open(extracted_app_folder + '/manifest.json') as f:
|
||||
manifest = json.loads(str(f.read()))
|
||||
except subprocess.CalledProcessError:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('app_sources_fetch_failed'))
|
||||
raise YunohostError('app_sources_fetch_failed')
|
||||
except ValueError as e:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('app_manifest_invalid', error=e.strerror))
|
||||
raise YunohostError('app_manifest_invalid', error=e.strerror)
|
||||
else:
|
||||
logger.debug(m18n.n('done'))
|
||||
|
||||
|
@ -2082,7 +2083,7 @@ def _check_manifest_requirements(manifest, app_instance_name):
|
|||
yunohost_req = requirements.get('yunohost', None)
|
||||
if (not yunohost_req or
|
||||
not packages.SpecifierSet(yunohost_req) & '>= 2.3.6'):
|
||||
raise MoulinetteError(errno.EINVAL, '{0}{1}'.format(
|
||||
raise YunohostError('{0}{1}'.format(
|
||||
m18n.g('colon', m18n.n('app_incompatible'), app=app_instance_name),
|
||||
m18n.n('app_package_need_update', app=app_instance_name)))
|
||||
elif not requirements:
|
||||
|
@ -2095,18 +2096,15 @@ def _check_manifest_requirements(manifest, app_instance_name):
|
|||
versions = packages.get_installed_version(
|
||||
*requirements.keys(), strict=True, as_dict=True)
|
||||
except packages.PackageException as e:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_requirements_failed',
|
||||
error=str(e), app=app_instance_name))
|
||||
raise YunohostError('app_requirements_failed', error=str(e), app=app_instance_name)
|
||||
|
||||
# Iterate over requirements
|
||||
for pkgname, spec in requirements.items():
|
||||
version = versions[pkgname]
|
||||
if version not in packages.SpecifierSet(spec):
|
||||
raise MoulinetteError(
|
||||
errno.EINVAL, m18n.n('app_requirements_unmeet',
|
||||
pkgname=pkgname, version=version,
|
||||
spec=spec, app=app_instance_name))
|
||||
raise YunohostError('app_requirements_unmeet',
|
||||
pkgname=pkgname, version=version,
|
||||
spec=spec, app=app_instance_name)
|
||||
|
||||
|
||||
def _parse_args_from_manifest(manifest, action, args={}, auth=None):
|
||||
|
@ -2206,11 +2204,14 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None):
|
|||
for domain in domain_list(auth)['domains']:
|
||||
msignals.display("- {}".format(domain))
|
||||
|
||||
if arg_type == 'user':
|
||||
elif arg_type == 'user':
|
||||
msignals.display(m18n.n('users_available'))
|
||||
for user in user_list(auth)['users'].keys():
|
||||
msignals.display("- {}".format(user))
|
||||
|
||||
elif arg_type == 'password':
|
||||
msignals.display(m18n.n('good_practices_about_user_password'))
|
||||
|
||||
try:
|
||||
input_string = msignals.prompt(ask_string, is_password)
|
||||
except NotImplementedError:
|
||||
|
@ -2226,36 +2227,27 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None):
|
|||
# Validate argument value
|
||||
if (arg_value is None or arg_value == '') \
|
||||
and not arg.get('optional', False):
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_argument_required', name=arg_name))
|
||||
raise YunohostError('app_argument_required', name=arg_name)
|
||||
elif arg_value is None:
|
||||
args_dict[arg_name] = ''
|
||||
continue
|
||||
|
||||
# Validate argument choice
|
||||
if arg_choices and arg_value not in arg_choices:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_argument_choice_invalid',
|
||||
name=arg_name, choices=', '.join(arg_choices)))
|
||||
raise YunohostError('app_argument_choice_invalid', name=arg_name, choices=', '.join(arg_choices))
|
||||
|
||||
# Validate argument type
|
||||
if arg_type == 'domain':
|
||||
if arg_value not in domain_list(auth)['domains']:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_argument_invalid',
|
||||
name=arg_name, error=m18n.n('domain_unknown')))
|
||||
raise YunohostError('app_argument_invalid', name=arg_name, error=m18n.n('domain_unknown'))
|
||||
elif arg_type == 'user':
|
||||
try:
|
||||
user_info(auth, arg_value)
|
||||
except MoulinetteError as e:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_argument_invalid',
|
||||
name=arg_name, error=e.strerror))
|
||||
except YunohostError as e:
|
||||
raise YunohostError('app_argument_invalid', name=arg_name, error=e.strerror)
|
||||
elif arg_type == 'app':
|
||||
if not _is_installed(arg_value):
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_argument_invalid',
|
||||
name=arg_name, error=m18n.n('app_unknown')))
|
||||
raise YunohostError('app_argument_invalid', name=arg_name, error=m18n.n('app_unknown'))
|
||||
elif arg_type == 'boolean':
|
||||
if isinstance(arg_value, bool):
|
||||
arg_value = 1 if arg_value else 0
|
||||
|
@ -2265,9 +2257,10 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None):
|
|||
elif str(arg_value).lower() in ["0", "no", "n"]:
|
||||
arg_value = 0
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_argument_choice_invalid',
|
||||
name=arg_name, choices='yes, no, y, n, 1, 0'))
|
||||
raise YunohostError('app_argument_choice_invalid', name=arg_name, choices='yes, no, y, n, 1, 0')
|
||||
elif arg_type == 'password':
|
||||
from yunohost.utils.password import assert_password_is_strong_enough
|
||||
assert_password_is_strong_enough('user', arg_value)
|
||||
args_dict[arg_name] = arg_value
|
||||
|
||||
# END loop over action_args...
|
||||
|
@ -2298,7 +2291,7 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None):
|
|||
app_label=app_label,
|
||||
))
|
||||
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('app_location_unavailable', apps="\n".join(apps)))
|
||||
raise YunohostError('app_location_unavailable', apps="\n".join(apps))
|
||||
|
||||
# (We save this normalized path so that the install script have a
|
||||
# standard path format to deal with no matter what the user inputted)
|
||||
|
@ -2418,7 +2411,7 @@ def _install_appslist_fetch_cron():
|
|||
with open(cron_job_file, "w") as f:
|
||||
f.write('\n'.join(cron_job))
|
||||
|
||||
_set_permissions(cron_job_file, "root", "root", 0755)
|
||||
_set_permissions(cron_job_file, "root", "root", 0o755)
|
||||
|
||||
|
||||
# FIXME - Duplicate from certificate.py, should be moved into a common helper
|
||||
|
@ -2448,8 +2441,7 @@ def _read_appslist_list():
|
|||
try:
|
||||
appslists = json.loads(appslists_json)
|
||||
except ValueError:
|
||||
raise MoulinetteError(errno.EBADR,
|
||||
m18n.n('appslist_corrupted_json', filename=APPSLISTS_JSON))
|
||||
raise YunohostError('appslist_corrupted_json', filename=APPSLISTS_JSON)
|
||||
|
||||
return appslists
|
||||
|
||||
|
@ -2464,9 +2456,8 @@ def _write_appslist_list(appslist_lists):
|
|||
with open(APPSLISTS_JSON, "w") as f:
|
||||
json.dump(appslist_lists, f)
|
||||
except Exception as e:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
"Error while writing list of appslist %s: %s" %
|
||||
(APPSLISTS_JSON, str(e)))
|
||||
raise YunohostError("Error while writing list of appslist %s: %s" %
|
||||
(APPSLISTS_JSON, str(e)), raw_msg=True)
|
||||
|
||||
|
||||
def _register_new_appslist(url, name):
|
||||
|
@ -2479,15 +2470,13 @@ def _register_new_appslist(url, name):
|
|||
|
||||
# Check if name conflicts with an existing list
|
||||
if name in appslist_list:
|
||||
raise MoulinetteError(errno.EEXIST,
|
||||
m18n.n('appslist_name_already_tracked', name=name))
|
||||
raise YunohostError('appslist_name_already_tracked', name=name)
|
||||
|
||||
# Check if url conflicts with an existing list
|
||||
known_appslist_urls = [appslist["url"] for _, appslist in appslist_list.items()]
|
||||
|
||||
if url in known_appslist_urls:
|
||||
raise MoulinetteError(errno.EEXIST,
|
||||
m18n.n('appslist_url_already_tracked', url=url))
|
||||
raise YunohostError('appslist_url_already_tracked', url=url)
|
||||
|
||||
logger.debug("Registering new appslist %s at %s" % (name, url))
|
||||
|
||||
|
@ -2578,7 +2567,7 @@ def _patch_php5(app_folder):
|
|||
continue
|
||||
|
||||
c = "sed -i -e 's@/etc/php5@/etc/php/7.0@g' " \
|
||||
"-e 's@/var/run/php5-fpm@/var/run/php/php7.0-fpm@g' " \
|
||||
"-e 's@php5@php7.0@g' " \
|
||||
"%s" % filename
|
||||
"-e 's@/var/run/php5-fpm@/var/run/php/php7.0-fpm@g' " \
|
||||
"-e 's@php5@php7.0@g' " \
|
||||
"%s" % filename
|
||||
os.system(c)
|
||||
|
|
|
@ -26,18 +26,18 @@
|
|||
import os
|
||||
import re
|
||||
import json
|
||||
import errno
|
||||
import time
|
||||
import tarfile
|
||||
import shutil
|
||||
import subprocess
|
||||
import csv
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from glob import glob
|
||||
from collections import OrderedDict
|
||||
|
||||
from moulinette import msignals, m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils import filesystem
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.filesystem import read_file
|
||||
|
@ -52,6 +52,7 @@ from yunohost.monitor import binary_to_human
|
|||
from yunohost.tools import tools_postinstall
|
||||
from yunohost.service import service_regen_conf
|
||||
from yunohost.log import OperationLogger
|
||||
from functools import reduce
|
||||
|
||||
BACKUP_PATH = '/home/yunohost.backup'
|
||||
ARCHIVES_PATH = '%s/archives' % BACKUP_PATH
|
||||
|
@ -63,6 +64,7 @@ logger = getActionLogger('yunohost.backup')
|
|||
|
||||
|
||||
class BackupRestoreTargetsManager(object):
|
||||
|
||||
"""
|
||||
BackupRestoreTargetsManager manage the targets
|
||||
in BackupManager and RestoreManager
|
||||
|
@ -176,6 +178,7 @@ class BackupRestoreTargetsManager(object):
|
|||
|
||||
|
||||
class BackupManager():
|
||||
|
||||
"""
|
||||
This class collect files to backup in a list and apply one or several
|
||||
backup method on it.
|
||||
|
@ -267,9 +270,9 @@ class BackupManager():
|
|||
self.work_dir = os.path.join(BACKUP_PATH, 'tmp', name)
|
||||
self._init_work_dir()
|
||||
|
||||
###########################################################################
|
||||
# Misc helpers #
|
||||
###########################################################################
|
||||
#
|
||||
# Misc helpers #
|
||||
#
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
|
@ -299,7 +302,7 @@ class BackupManager():
|
|||
(string) A backup name created from current date 'YYMMDD-HHMMSS'
|
||||
"""
|
||||
# FIXME: case where this name already exist
|
||||
return time.strftime('%Y%m%d-%H%M%S')
|
||||
return time.strftime('%Y%m%d-%H%M%S', time.gmtime())
|
||||
|
||||
def _init_work_dir(self):
|
||||
"""Initialize preparation directory
|
||||
|
@ -307,31 +310,30 @@ class BackupManager():
|
|||
Ensure the working directory exists and is empty
|
||||
|
||||
exception:
|
||||
backup_output_directory_not_empty -- (MoulinetteError) Raised if the
|
||||
backup_output_directory_not_empty -- (YunohostError) Raised if the
|
||||
directory was given by the user and isn't empty
|
||||
|
||||
(TODO) backup_cant_clean_tmp_working_directory -- (MoulinetteError)
|
||||
(TODO) backup_cant_clean_tmp_working_directory -- (YunohostError)
|
||||
Raised if the working directory isn't empty, is temporary and can't
|
||||
be automaticcaly cleaned
|
||||
|
||||
(TODO) backup_cant_create_working_directory -- (MoulinetteError) Raised
|
||||
(TODO) backup_cant_create_working_directory -- (YunohostError) Raised
|
||||
if iyunohost can't create the working directory
|
||||
"""
|
||||
|
||||
# FIXME replace isdir by exists ? manage better the case where the path
|
||||
# exists
|
||||
if not os.path.isdir(self.work_dir):
|
||||
filesystem.mkdir(self.work_dir, 0750, parents=True, uid='admin')
|
||||
filesystem.mkdir(self.work_dir, 0o750, parents=True, uid='admin')
|
||||
elif self.is_tmp_work_dir:
|
||||
logger.debug("temporary directory for backup '%s' already exists",
|
||||
self.work_dir)
|
||||
# FIXME May be we should clean the workdir here
|
||||
raise MoulinetteError(
|
||||
errno.EIO, m18n.n('backup_output_directory_not_empty'))
|
||||
raise YunohostError('backup_output_directory_not_empty')
|
||||
|
||||
###########################################################################
|
||||
# Backup target management #
|
||||
###########################################################################
|
||||
#
|
||||
# Backup target management #
|
||||
#
|
||||
|
||||
def set_system_targets(self, system_parts=[]):
|
||||
"""
|
||||
|
@ -381,9 +383,9 @@ class BackupManager():
|
|||
logger.warning(m18n.n('backup_with_no_restore_script_for_app', app=app))
|
||||
self.targets.set_result("apps", app, "Warning")
|
||||
|
||||
###########################################################################
|
||||
# Management of files to backup / "The CSV" #
|
||||
###########################################################################
|
||||
#
|
||||
# Management of files to backup / "The CSV" #
|
||||
#
|
||||
|
||||
def _import_to_list_to_backup(self, tmp_csv):
|
||||
"""
|
||||
|
@ -466,9 +468,9 @@ class BackupManager():
|
|||
logger.error(m18n.n('backup_csv_addition_failed'))
|
||||
self.csv_file.close()
|
||||
|
||||
###########################################################################
|
||||
# File collection from system parts and apps #
|
||||
###########################################################################
|
||||
#
|
||||
# File collection from system parts and apps #
|
||||
#
|
||||
|
||||
def collect_files(self):
|
||||
"""
|
||||
|
@ -493,7 +495,7 @@ class BackupManager():
|
|||
copied here
|
||||
|
||||
Exceptions:
|
||||
"backup_nothings_done" -- (MoulinetteError) This exception is raised if
|
||||
"backup_nothings_done" -- (YunohostError) This exception is raised if
|
||||
nothing has been listed.
|
||||
"""
|
||||
|
||||
|
@ -506,7 +508,7 @@ class BackupManager():
|
|||
|
||||
if not successfull_apps and not successfull_system:
|
||||
filesystem.rm(self.work_dir, True, True)
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('backup_nothings_done'))
|
||||
raise YunohostError('backup_nothings_done')
|
||||
|
||||
# Add unlisted files from backup tmp dir
|
||||
self._add_to_list_to_backup('backup.csv')
|
||||
|
@ -603,7 +605,7 @@ class BackupManager():
|
|||
|
||||
restore_hooks_dir = os.path.join(self.work_dir, "hooks", "restore")
|
||||
if not os.path.exists(restore_hooks_dir):
|
||||
filesystem.mkdir(restore_hooks_dir, mode=0750,
|
||||
filesystem.mkdir(restore_hooks_dir, mode=0o750,
|
||||
parents=True, uid='admin')
|
||||
|
||||
restore_hooks = hook_list("restore")["hooks"]
|
||||
|
@ -669,7 +671,7 @@ class BackupManager():
|
|||
logger.debug(m18n.n('backup_running_app_script', app=app))
|
||||
try:
|
||||
# Prepare backup directory for the app
|
||||
filesystem.mkdir(tmp_app_bkp_dir, 0750, True, uid='admin')
|
||||
filesystem.mkdir(tmp_app_bkp_dir, 0o750, True, uid='admin')
|
||||
|
||||
# Copy the app settings to be able to call _common.sh
|
||||
shutil.copytree(app_setting_path, settings_dir)
|
||||
|
@ -703,9 +705,9 @@ class BackupManager():
|
|||
filesystem.rm(tmp_script, force=True)
|
||||
filesystem.rm(env_dict["YNH_BACKUP_CSV"], force=True)
|
||||
|
||||
###########################################################################
|
||||
# Actual backup archive creation / method management #
|
||||
###########################################################################
|
||||
#
|
||||
# Actual backup archive creation / method management #
|
||||
#
|
||||
|
||||
def add(self, method):
|
||||
"""
|
||||
|
@ -777,6 +779,7 @@ class BackupManager():
|
|||
|
||||
|
||||
class RestoreManager():
|
||||
|
||||
"""
|
||||
RestoreManager allow to restore a past backup archive
|
||||
|
||||
|
@ -825,9 +828,9 @@ class RestoreManager():
|
|||
self.method = BackupMethod.create(method)
|
||||
self.targets = BackupRestoreTargetsManager()
|
||||
|
||||
###########################################################################
|
||||
# Misc helpers #
|
||||
###########################################################################
|
||||
#
|
||||
# Misc helpers #
|
||||
#
|
||||
|
||||
@property
|
||||
def success(self):
|
||||
|
@ -856,10 +859,10 @@ class RestoreManager():
|
|||
self.info["system"] = self.info["hooks"]
|
||||
except IOError:
|
||||
logger.debug("unable to load '%s'", info_file, exc_info=1)
|
||||
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
|
||||
raise YunohostError('backup_invalid_archive')
|
||||
else:
|
||||
logger.debug("restoring from backup '%s' created on %s", self.name,
|
||||
time.ctime(self.info['created_at']))
|
||||
datetime.utcfromtimestamp(self.info['created_at']))
|
||||
|
||||
def _postinstall_if_needed(self):
|
||||
"""
|
||||
|
@ -877,10 +880,9 @@ class RestoreManager():
|
|||
domain = f.readline().rstrip()
|
||||
except IOError:
|
||||
logger.debug("unable to retrieve current_host from the backup",
|
||||
exc_info=1)
|
||||
exc_info=1)
|
||||
# FIXME include the current_host by default ?
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_invalid_archive'))
|
||||
raise YunohostError('backup_invalid_archive')
|
||||
|
||||
logger.debug("executing the post-install...")
|
||||
tools_postinstall(domain, 'yunohost', True)
|
||||
|
@ -904,9 +906,9 @@ class RestoreManager():
|
|||
logger.warning(m18n.n('restore_cleaning_failed'))
|
||||
filesystem.rm(self.work_dir, True, True)
|
||||
|
||||
###########################################################################
|
||||
# Restore target manangement #
|
||||
###########################################################################
|
||||
#
|
||||
# Restore target manangement #
|
||||
#
|
||||
|
||||
def set_system_targets(self, system_parts=[]):
|
||||
"""
|
||||
|
@ -982,9 +984,9 @@ class RestoreManager():
|
|||
self.info['apps'].keys(),
|
||||
unknown_error)
|
||||
|
||||
###########################################################################
|
||||
# Archive mounting #
|
||||
###########################################################################
|
||||
#
|
||||
# Archive mounting #
|
||||
#
|
||||
|
||||
def mount(self):
|
||||
"""
|
||||
|
@ -1009,8 +1011,7 @@ class RestoreManager():
|
|||
subprocess.call(['rmdir', self.work_dir])
|
||||
logger.debug("Unmount dir: {}".format(self.work_dir))
|
||||
else:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('restore_removing_tmp_dir_failed'))
|
||||
raise YunohostError('restore_removing_tmp_dir_failed')
|
||||
elif os.path.isdir(self.work_dir):
|
||||
logger.debug("temporary restore directory '%s' already exists",
|
||||
self.work_dir)
|
||||
|
@ -1018,8 +1019,7 @@ class RestoreManager():
|
|||
if ret == 0:
|
||||
logger.debug("Delete dir: {}".format(self.work_dir))
|
||||
else:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('restore_removing_tmp_dir_failed'))
|
||||
raise YunohostError('restore_removing_tmp_dir_failed')
|
||||
|
||||
filesystem.mkdir(self.work_dir, parents=True)
|
||||
|
||||
|
@ -1027,9 +1027,9 @@ class RestoreManager():
|
|||
|
||||
self._read_info_files()
|
||||
|
||||
###########################################################################
|
||||
# Space computation / checks #
|
||||
###########################################################################
|
||||
#
|
||||
# Space computation / checks #
|
||||
#
|
||||
|
||||
def _compute_needed_space(self):
|
||||
"""
|
||||
|
@ -1086,21 +1086,13 @@ class RestoreManager():
|
|||
return True
|
||||
elif free_space > needed_space:
|
||||
# TODO Add --force options to avoid the error raising
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('restore_may_be_not_enough_disk_space',
|
||||
free_space=free_space,
|
||||
needed_space=needed_space,
|
||||
margin=margin))
|
||||
raise YunohostError('restore_may_be_not_enough_disk_space', free_space=free_space, needed_space=needed_space, margin=margin)
|
||||
else:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('restore_not_enough_disk_space',
|
||||
free_space=free_space,
|
||||
needed_space=needed_space,
|
||||
margin=margin))
|
||||
raise YunohostError('restore_not_enough_disk_space', free_space=free_space, needed_space=needed_space, margin=margin)
|
||||
|
||||
###########################################################################
|
||||
# "Actual restore" (reverse step of the backup collect part) #
|
||||
###########################################################################
|
||||
#
|
||||
# "Actual restore" (reverse step of the backup collect part) #
|
||||
#
|
||||
|
||||
def restore(self):
|
||||
"""
|
||||
|
@ -1116,7 +1108,6 @@ class RestoreManager():
|
|||
# Apply dirty patch to redirect php5 file on php7
|
||||
self._patch_backup_csv_file()
|
||||
|
||||
|
||||
self._restore_system()
|
||||
self._restore_apps()
|
||||
finally:
|
||||
|
@ -1142,13 +1133,11 @@ class RestoreManager():
|
|||
contains_php5 = True
|
||||
row['source'] = row['source'].replace('/etc/php5', '/etc/php/7.0') \
|
||||
.replace('/var/run/php5-fpm', '/var/run/php/php7.0-fpm') \
|
||||
.replace('php5','php7')
|
||||
.replace('php5', 'php7')
|
||||
|
||||
newlines.append(row)
|
||||
except (IOError, OSError, csv.Error) as e:
|
||||
raise MoulinetteError(errno.EIO,m18n.n('error_reading_file',
|
||||
file=backup_csv,
|
||||
error=str(e)))
|
||||
raise YunohostError('error_reading_file', file=backup_csv, error=str(e))
|
||||
|
||||
if not contains_php5:
|
||||
return
|
||||
|
@ -1274,7 +1263,7 @@ class RestoreManager():
|
|||
|
||||
# Check if the app has a restore script
|
||||
app_restore_script_in_archive = os.path.join(app_scripts_in_archive,
|
||||
'restore')
|
||||
'restore')
|
||||
if not os.path.isfile(app_restore_script_in_archive):
|
||||
logger.warning(m18n.n('unrestore_app', app=app_instance_name))
|
||||
self.targets.set_result("apps", app_instance_name, "Warning")
|
||||
|
@ -1287,7 +1276,7 @@ class RestoreManager():
|
|||
app_instance_name)
|
||||
app_scripts_new_path = os.path.join(app_settings_new_path, 'scripts')
|
||||
shutil.copytree(app_settings_in_archive, app_settings_new_path)
|
||||
filesystem.chmod(app_settings_new_path, 0400, 0400, True)
|
||||
filesystem.chmod(app_settings_new_path, 0o400, 0o400, True)
|
||||
filesystem.chown(app_scripts_new_path, 'admin', None, True)
|
||||
|
||||
# Copy the app scripts to a writable temporary folder
|
||||
|
@ -1295,7 +1284,7 @@ class RestoreManager():
|
|||
# in the backup method ?
|
||||
tmp_folder_for_app_restore = tempfile.mkdtemp(prefix='restore')
|
||||
copytree(app_scripts_in_archive, tmp_folder_for_app_restore)
|
||||
filesystem.chmod(tmp_folder_for_app_restore, 0550, 0550, True)
|
||||
filesystem.chmod(tmp_folder_for_app_restore, 0o550, 0o550, True)
|
||||
filesystem.chown(tmp_folder_for_app_restore, 'admin', None, True)
|
||||
restore_script = os.path.join(tmp_folder_for_app_restore, 'restore')
|
||||
|
||||
|
@ -1312,7 +1301,7 @@ class RestoreManager():
|
|||
raise_on_error=True,
|
||||
env=env_dict)
|
||||
except:
|
||||
msg = m18n.n('restore_app_failed',app=app_instance_name)
|
||||
msg = m18n.n('restore_app_failed', app=app_instance_name)
|
||||
logger.exception(msg)
|
||||
operation_logger.error(msg)
|
||||
|
||||
|
@ -1328,8 +1317,8 @@ class RestoreManager():
|
|||
env_dict_remove["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
|
||||
|
||||
operation_logger = OperationLogger('remove_on_failed_restore',
|
||||
[('app', app_instance_name)],
|
||||
env=env_dict_remove)
|
||||
[('app', app_instance_name)],
|
||||
env=env_dict_remove)
|
||||
operation_logger.start()
|
||||
|
||||
# Execute remove script
|
||||
|
@ -1373,12 +1362,13 @@ class RestoreManager():
|
|||
|
||||
return env_var
|
||||
|
||||
###############################################################################
|
||||
# Backup methods #
|
||||
###############################################################################
|
||||
#
|
||||
# Backup methods #
|
||||
#
|
||||
|
||||
|
||||
class BackupMethod(object):
|
||||
|
||||
"""
|
||||
BackupMethod is an abstract class that represents a way to backup and
|
||||
restore a list of files.
|
||||
|
@ -1441,7 +1431,7 @@ class BackupMethod(object):
|
|||
@property
|
||||
def method_name(self):
|
||||
"""Return the string name of a BackupMethod (eg "tar" or "copy")"""
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('backup_abstract_method'))
|
||||
raise YunohostError('backup_abstract_method')
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -1523,8 +1513,7 @@ class BackupMethod(object):
|
|||
"""
|
||||
if self.need_mount():
|
||||
if self._recursive_umount(self.work_dir) > 0:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('backup_cleaning_failed'))
|
||||
raise YunohostError('backup_cleaning_failed')
|
||||
|
||||
if self.manager.is_tmp_work_dir:
|
||||
filesystem.rm(self.work_dir, True, True)
|
||||
|
@ -1566,8 +1555,7 @@ class BackupMethod(object):
|
|||
if free_space < backup_size:
|
||||
logger.debug('Not enough space at %s (free: %s / needed: %d)',
|
||||
self.repo, free_space, backup_size)
|
||||
raise MoulinetteError(errno.EIO, m18n.n(
|
||||
'not_enough_disk_space', path=self.repo))
|
||||
raise YunohostError('not_enough_disk_space', path=self.repo)
|
||||
|
||||
def _organize_files(self):
|
||||
"""
|
||||
|
@ -1653,18 +1641,16 @@ class BackupMethod(object):
|
|||
if size > MB_ALLOWED_TO_ORGANIZE:
|
||||
try:
|
||||
i = msignals.prompt(m18n.n('backup_ask_for_copying_if_needed',
|
||||
answers='y/N', size=str(size)))
|
||||
answers='y/N', size=str(size)))
|
||||
except NotImplemented:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_unable_to_organize_files'))
|
||||
raise YunohostError('backup_unable_to_organize_files')
|
||||
else:
|
||||
if i != 'y' and i != 'Y':
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_unable_to_organize_files'))
|
||||
raise YunohostError('backup_unable_to_organize_files')
|
||||
|
||||
# Copy unbinded path
|
||||
logger.debug(m18n.n('backup_copying_to_organize_the_archive',
|
||||
size=str(size)))
|
||||
size=str(size)))
|
||||
for path in paths_needed_to_be_copied:
|
||||
dest = os.path.join(self.work_dir, path['dest'])
|
||||
if os.path.isdir(path['source']):
|
||||
|
@ -1704,6 +1690,7 @@ class BackupMethod(object):
|
|||
|
||||
|
||||
class CopyBackupMethod(BackupMethod):
|
||||
|
||||
"""
|
||||
This class just do an uncompress copy of each file in a location, and
|
||||
could be the inverse for restoring
|
||||
|
@ -1730,7 +1717,7 @@ class CopyBackupMethod(BackupMethod):
|
|||
|
||||
dest_parent = os.path.dirname(dest)
|
||||
if not os.path.exists(dest_parent):
|
||||
filesystem.mkdir(dest_parent, 0750, True, uid='admin')
|
||||
filesystem.mkdir(dest_parent, 0o750, True, uid='admin')
|
||||
|
||||
if os.path.isdir(source):
|
||||
shutil.copytree(source, dest)
|
||||
|
@ -1750,8 +1737,7 @@ class CopyBackupMethod(BackupMethod):
|
|||
super(CopyBackupMethod, self).mount()
|
||||
|
||||
if not os.path.isdir(self.repo):
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_no_uncompress_archive_dir'))
|
||||
raise YunohostError('backup_no_uncompress_archive_dir')
|
||||
|
||||
filesystem.mkdir(self.work_dir, parent=True)
|
||||
ret = subprocess.call(["mount", "-r", "--rbind", self.repo,
|
||||
|
@ -1762,11 +1748,11 @@ class CopyBackupMethod(BackupMethod):
|
|||
logger.warning(m18n.n("bind_mouting_disable"))
|
||||
subprocess.call(["mountpoint", "-q", dest,
|
||||
"&&", "umount", "-R", dest])
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_cant_mount_uncompress_archive'))
|
||||
raise YunohostError('backup_cant_mount_uncompress_archive')
|
||||
|
||||
|
||||
class TarBackupMethod(BackupMethod):
|
||||
|
||||
"""
|
||||
This class compress all files to backup in archive.
|
||||
"""
|
||||
|
@ -1797,7 +1783,7 @@ class TarBackupMethod(BackupMethod):
|
|||
"""
|
||||
|
||||
if not os.path.exists(self.repo):
|
||||
filesystem.mkdir(self.repo, 0750, parents=True, uid='admin')
|
||||
filesystem.mkdir(self.repo, 0o750, parents=True, uid='admin')
|
||||
|
||||
# Check free space in output
|
||||
self._check_is_enough_free_space()
|
||||
|
@ -1808,8 +1794,7 @@ class TarBackupMethod(BackupMethod):
|
|||
except:
|
||||
logger.debug("unable to open '%s' for writing",
|
||||
self._archive_file, exc_info=1)
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_archive_open_failed'))
|
||||
raise YunohostError('backup_archive_open_failed')
|
||||
|
||||
# Add files to the archive
|
||||
try:
|
||||
|
@ -1820,8 +1805,7 @@ class TarBackupMethod(BackupMethod):
|
|||
tar.close()
|
||||
except IOError:
|
||||
logger.error(m18n.n('backup_archive_writing_error'), exc_info=1)
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_creation_failed'))
|
||||
raise YunohostError('backup_creation_failed')
|
||||
|
||||
# Move info file
|
||||
shutil.copy(os.path.join(self.work_dir, 'info.json'),
|
||||
|
@ -1849,8 +1833,7 @@ class TarBackupMethod(BackupMethod):
|
|||
except:
|
||||
logger.debug("cannot open backup archive '%s'",
|
||||
self._archive_file, exc_info=1)
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_archive_open_failed'))
|
||||
raise YunohostError('backup_archive_open_failed')
|
||||
tar.close()
|
||||
|
||||
# Mount the tarball
|
||||
|
@ -1911,15 +1894,14 @@ class BorgBackupMethod(BackupMethod):
|
|||
super(CopyBackupMethod, self).backup()
|
||||
|
||||
# TODO run borg create command
|
||||
raise MoulinetteError(
|
||||
errno.EIO, m18n.n('backup_borg_not_implemented'))
|
||||
raise YunohostError('backup_borg_not_implemented')
|
||||
|
||||
def mount(self, mnt_path):
|
||||
raise MoulinetteError(
|
||||
errno.EIO, m18n.n('backup_borg_not_implemented'))
|
||||
raise YunohostError('backup_borg_not_implemented')
|
||||
|
||||
|
||||
class CustomBackupMethod(BackupMethod):
|
||||
|
||||
"""
|
||||
This class use a bash script/hook "backup_method" to do the
|
||||
backup/restore operations. A user can add his own hook inside
|
||||
|
@ -1962,8 +1944,7 @@ class CustomBackupMethod(BackupMethod):
|
|||
ret = hook_callback('backup_method', [self.method],
|
||||
args=self._get_args('backup'))
|
||||
if ret['failed']:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_custom_backup_error'))
|
||||
raise YunohostError('backup_custom_backup_error')
|
||||
|
||||
def mount(self, restore_manager):
|
||||
"""
|
||||
|
@ -1976,8 +1957,7 @@ class CustomBackupMethod(BackupMethod):
|
|||
ret = hook_callback('backup_method', [self.method],
|
||||
args=self._get_args('mount'))
|
||||
if ret['failed']:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_custom_mount_error'))
|
||||
raise YunohostError('backup_custom_mount_error')
|
||||
|
||||
def _get_args(self, action):
|
||||
"""Return the arguments to give to the custom script"""
|
||||
|
@ -1985,9 +1965,9 @@ class CustomBackupMethod(BackupMethod):
|
|||
self.manager.description]
|
||||
|
||||
|
||||
###############################################################################
|
||||
# "Front-end" #
|
||||
###############################################################################
|
||||
#
|
||||
# "Front-end" #
|
||||
#
|
||||
|
||||
def backup_create(name=None, description=None, methods=[],
|
||||
output_directory=None, no_compress=False,
|
||||
|
@ -2007,14 +1987,13 @@ def backup_create(name=None, description=None, methods=[],
|
|||
|
||||
# TODO: Add a 'clean' argument to clean output directory
|
||||
|
||||
###########################################################################
|
||||
# Validate / parse arguments #
|
||||
###########################################################################
|
||||
#
|
||||
# Validate / parse arguments #
|
||||
#
|
||||
|
||||
# Validate there is no archive with the same name
|
||||
if name and name in backup_list()['archives']:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('backup_archive_name_exists'))
|
||||
raise YunohostError('backup_archive_name_exists')
|
||||
|
||||
# Validate output_directory option
|
||||
if output_directory:
|
||||
|
@ -2023,18 +2002,15 @@ def backup_create(name=None, description=None, methods=[],
|
|||
# Check for forbidden folders
|
||||
if output_directory.startswith(ARCHIVES_PATH) or \
|
||||
re.match(r'^/(|(bin|boot|dev|etc|lib|root|run|sbin|sys|usr|var)(|/.*))$',
|
||||
output_directory):
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('backup_output_directory_forbidden'))
|
||||
output_directory):
|
||||
raise YunohostError('backup_output_directory_forbidden')
|
||||
|
||||
# Check that output directory is empty
|
||||
if os.path.isdir(output_directory) and no_compress and \
|
||||
os.listdir(output_directory):
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_output_directory_not_empty'))
|
||||
raise YunohostError('backup_output_directory_not_empty')
|
||||
elif no_compress:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('backup_output_directory_required'))
|
||||
raise YunohostError('backup_output_directory_required')
|
||||
|
||||
# Define methods (retro-compat)
|
||||
if not methods:
|
||||
|
@ -2048,9 +2024,9 @@ def backup_create(name=None, description=None, methods=[],
|
|||
system = []
|
||||
apps = []
|
||||
|
||||
###########################################################################
|
||||
# Intialize #
|
||||
###########################################################################
|
||||
#
|
||||
# Intialize #
|
||||
#
|
||||
|
||||
# Create yunohost archives directory if it does not exists
|
||||
_create_archive_dir()
|
||||
|
@ -2075,9 +2051,9 @@ def backup_create(name=None, description=None, methods=[],
|
|||
backup_manager.set_system_targets(system)
|
||||
backup_manager.set_apps_targets(apps)
|
||||
|
||||
###########################################################################
|
||||
# Collect files and put them in the archive #
|
||||
###########################################################################
|
||||
#
|
||||
# Collect files and put them in the archive #
|
||||
#
|
||||
|
||||
# Collect files to be backup (by calling app backup script / system hooks)
|
||||
backup_manager.collect_files()
|
||||
|
@ -2105,9 +2081,9 @@ def backup_restore(auth, name, system=[], apps=[], force=False):
|
|||
apps -- List of application names to restore
|
||||
"""
|
||||
|
||||
###########################################################################
|
||||
# Validate / parse arguments #
|
||||
###########################################################################
|
||||
#
|
||||
# Validate / parse arguments #
|
||||
#
|
||||
|
||||
# If no --system or --apps given, restore everything
|
||||
if system is None and apps is None:
|
||||
|
@ -2131,14 +2107,14 @@ def backup_restore(auth, name, system=[], apps=[], force=False):
|
|||
if i == 'y' or i == 'Y':
|
||||
force = True
|
||||
if not force:
|
||||
raise MoulinetteError(errno.EEXIST, m18n.n('restore_failed'))
|
||||
raise YunohostError('restore_failed')
|
||||
|
||||
# TODO Partial app restore could not work if ldap is not restored before
|
||||
# TODO repair mysql if broken and it's a complete restore
|
||||
|
||||
###########################################################################
|
||||
# Initialize #
|
||||
###########################################################################
|
||||
#
|
||||
# Initialize #
|
||||
#
|
||||
|
||||
restore_manager = RestoreManager(name)
|
||||
|
||||
|
@ -2147,9 +2123,9 @@ def backup_restore(auth, name, system=[], apps=[], force=False):
|
|||
|
||||
restore_manager.assert_enough_free_space()
|
||||
|
||||
###########################################################################
|
||||
# Mount the archive then call the restore for each system part / app #
|
||||
###########################################################################
|
||||
#
|
||||
# Mount the archive then call the restore for each system part / app #
|
||||
#
|
||||
|
||||
restore_manager.mount()
|
||||
restore_manager.restore()
|
||||
|
@ -2158,7 +2134,7 @@ def backup_restore(auth, name, system=[], apps=[], force=False):
|
|||
if restore_manager.success:
|
||||
logger.success(m18n.n('restore_complete'))
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('restore_nothings_done'))
|
||||
raise YunohostError('restore_nothings_done')
|
||||
|
||||
return restore_manager.targets.results
|
||||
|
||||
|
@ -2187,14 +2163,14 @@ def backup_list(with_info=False, human_readable=False):
|
|||
except ValueError:
|
||||
continue
|
||||
result.append(name)
|
||||
result.sort()
|
||||
result.sort(key=lambda x: os.path.getctime(os.path.join(ARCHIVES_PATH, x + ".tar.gz")))
|
||||
|
||||
if result and with_info:
|
||||
d = OrderedDict()
|
||||
for a in result:
|
||||
try:
|
||||
d[a] = backup_info(a, human_readable=human_readable)
|
||||
except MoulinetteError, e:
|
||||
except YunohostError as e:
|
||||
logger.warning('%s: %s' % (a, e.strerror))
|
||||
|
||||
result = d
|
||||
|
@ -2216,8 +2192,7 @@ def backup_info(name, with_details=False, human_readable=False):
|
|||
|
||||
# Check file exist (even if it's a broken symlink)
|
||||
if not os.path.lexists(archive_file):
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_archive_name_unknown', name=name))
|
||||
raise YunohostError('backup_archive_name_unknown', name=name)
|
||||
|
||||
# If symlink, retrieve the real path
|
||||
if os.path.islink(archive_file):
|
||||
|
@ -2225,9 +2200,8 @@ def backup_info(name, with_details=False, human_readable=False):
|
|||
|
||||
# Raise exception if link is broken (e.g. on unmounted external storage)
|
||||
if not os.path.exists(archive_file):
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_archive_broken_link',
|
||||
path=archive_file))
|
||||
raise YunohostError('backup_archive_broken_link',
|
||||
path=archive_file)
|
||||
|
||||
info_file = "%s/%s.info.json" % (ARCHIVES_PATH, name)
|
||||
|
||||
|
@ -2239,7 +2213,7 @@ def backup_info(name, with_details=False, human_readable=False):
|
|||
except KeyError:
|
||||
logger.debug("unable to retrieve '%s' inside the archive",
|
||||
info_file, exc_info=1)
|
||||
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
|
||||
raise YunohostError('backup_invalid_archive')
|
||||
else:
|
||||
shutil.move(os.path.join(info_dir, 'info.json'), info_file)
|
||||
finally:
|
||||
|
@ -2252,7 +2226,7 @@ def backup_info(name, with_details=False, human_readable=False):
|
|||
info = json.load(f)
|
||||
except:
|
||||
logger.debug("unable to load '%s'", info_file, exc_info=1)
|
||||
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
|
||||
raise YunohostError('backup_invalid_archive')
|
||||
|
||||
# Retrieve backup size
|
||||
size = info.get('size', 0)
|
||||
|
@ -2266,8 +2240,7 @@ def backup_info(name, with_details=False, human_readable=False):
|
|||
|
||||
result = {
|
||||
'path': archive_file,
|
||||
'created_at': time.strftime(m18n.n('format_datetime_short'),
|
||||
time.gmtime(info['created_at'])),
|
||||
'created_at': datetime.utcfromtimestamp(info['created_at']),
|
||||
'description': info['description'],
|
||||
'size': size,
|
||||
}
|
||||
|
@ -2292,8 +2265,8 @@ def backup_delete(name):
|
|||
|
||||
"""
|
||||
if name not in backup_list()["archives"]:
|
||||
raise MoulinetteError(errno.EIO, m18n.n('backup_archive_name_unknown',
|
||||
name=name))
|
||||
raise YunohostError('backup_archive_name_unknown',
|
||||
name=name)
|
||||
|
||||
hook_callback('pre_backup_delete', args=[name])
|
||||
|
||||
|
@ -2311,20 +2284,18 @@ def backup_delete(name):
|
|||
|
||||
logger.success(m18n.n('backup_deleted'))
|
||||
|
||||
###############################################################################
|
||||
# Misc helpers #
|
||||
###############################################################################
|
||||
#
|
||||
# Misc helpers #
|
||||
#
|
||||
|
||||
|
||||
def _create_archive_dir():
|
||||
""" Create the YunoHost archives directory if doesn't exist """
|
||||
if not os.path.isdir(ARCHIVES_PATH):
|
||||
if os.path.lexists(ARCHIVES_PATH):
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('backup_output_symlink_dir_broken',
|
||||
path=ARCHIVES_PATH))
|
||||
raise YunohostError('backup_output_symlink_dir_broken', path=ARCHIVES_PATH)
|
||||
|
||||
os.mkdir(ARCHIVES_PATH, 0750)
|
||||
os.mkdir(ARCHIVES_PATH, 0o750)
|
||||
|
||||
|
||||
def _call_for_each_path(self, callback, csv_path=None):
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
|
||||
import os
|
||||
import sys
|
||||
import errno
|
||||
import shutil
|
||||
import pwd
|
||||
import grp
|
||||
|
@ -37,7 +36,7 @@ from datetime import datetime
|
|||
|
||||
from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate
|
||||
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
from yunohost.utils.network import get_public_ip
|
||||
|
@ -81,9 +80,9 @@ DNS_RESOLVERS = [
|
|||
"80.67.188.188" # LDN
|
||||
]
|
||||
|
||||
###############################################################################
|
||||
# Front-end stuff #
|
||||
###############################################################################
|
||||
#
|
||||
# Front-end stuff #
|
||||
#
|
||||
|
||||
|
||||
def certificate_status(auth, domain_list, full=False):
|
||||
|
@ -106,8 +105,7 @@ def certificate_status(auth, domain_list, full=False):
|
|||
for domain in domain_list:
|
||||
# Is it in Yunohost domain list?
|
||||
if domain not in yunohost_domains_list:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_domain_unknown', domain=domain))
|
||||
raise YunohostError('certmanager_domain_unknown', domain=domain)
|
||||
|
||||
certificates = {}
|
||||
|
||||
|
@ -151,10 +149,10 @@ def _certificate_install_selfsigned(domain_list, force=False):
|
|||
for domain in domain_list:
|
||||
|
||||
operation_logger = OperationLogger('selfsigned_cert_install', [('domain', domain)],
|
||||
args={'force': force})
|
||||
args={'force': force})
|
||||
|
||||
# Paths of files and folder we'll need
|
||||
date_tag = datetime.now().strftime("%Y%m%d.%H%M%S")
|
||||
date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S")
|
||||
new_cert_folder = "%s/%s-history/%s-selfsigned" % (
|
||||
CERT_FOLDER, domain, date_tag)
|
||||
|
||||
|
@ -172,8 +170,7 @@ def _certificate_install_selfsigned(domain_list, force=False):
|
|||
status = _get_status(domain)
|
||||
|
||||
if status["summary"]["code"] in ('good', 'great'):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_attempt_to_replace_valid_cert', domain=domain))
|
||||
raise YunohostError('certmanager_attempt_to_replace_valid_cert', domain=domain)
|
||||
|
||||
operation_logger.start()
|
||||
|
||||
|
@ -203,8 +200,7 @@ def _certificate_install_selfsigned(domain_list, force=False):
|
|||
|
||||
if p.returncode != 0:
|
||||
logger.warning(out)
|
||||
raise MoulinetteError(
|
||||
errno.EIO, m18n.n('domain_cert_gen_failed'))
|
||||
raise YunohostError('domain_cert_gen_failed')
|
||||
else:
|
||||
logger.debug(out)
|
||||
|
||||
|
@ -219,10 +215,10 @@ def _certificate_install_selfsigned(domain_list, force=False):
|
|||
crt_pem.write(ca_pem.read())
|
||||
|
||||
# Set appropriate permissions
|
||||
_set_permissions(new_cert_folder, "root", "root", 0755)
|
||||
_set_permissions(key_file, "root", "ssl-cert", 0640)
|
||||
_set_permissions(crt_file, "root", "ssl-cert", 0640)
|
||||
_set_permissions(conf_file, "root", "root", 0600)
|
||||
_set_permissions(new_cert_folder, "root", "root", 0o755)
|
||||
_set_permissions(key_file, "root", "ssl-cert", 0o640)
|
||||
_set_permissions(crt_file, "root", "ssl-cert", 0o640)
|
||||
_set_permissions(conf_file, "root", "root", 0o600)
|
||||
|
||||
# Actually enable the certificate we created
|
||||
_enable_certificate(domain, new_cert_folder)
|
||||
|
@ -262,14 +258,12 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F
|
|||
for domain in domain_list:
|
||||
yunohost_domains_list = yunohost.domain.domain_list(auth)['domains']
|
||||
if domain not in yunohost_domains_list:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_domain_unknown', domain=domain))
|
||||
raise YunohostError('certmanager_domain_unknown', domain=domain)
|
||||
|
||||
# Is it self-signed?
|
||||
status = _get_status(domain)
|
||||
if not force and status["CA_type"]["code"] != "self-signed":
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_domain_cert_not_selfsigned', domain=domain))
|
||||
raise YunohostError('certmanager_domain_cert_not_selfsigned', domain=domain)
|
||||
|
||||
if staging:
|
||||
logger.warning(
|
||||
|
@ -279,8 +273,8 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F
|
|||
for domain in domain_list:
|
||||
|
||||
operation_logger = OperationLogger('letsencrypt_cert_install', [('domain', domain)],
|
||||
args={'force': force, 'no_checks': no_checks,
|
||||
'staging': staging})
|
||||
args={'force': force, 'no_checks': no_checks,
|
||||
'staging': staging})
|
||||
logger.info(
|
||||
"Now attempting install of certificate for domain %s!", domain)
|
||||
|
||||
|
@ -304,6 +298,7 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F
|
|||
logger.error(msg)
|
||||
operation_logger.error(msg)
|
||||
|
||||
|
||||
def certificate_renew(auth, domain_list, force=False, no_checks=False, email=False, staging=False):
|
||||
"""
|
||||
Renew Let's Encrypt certificate for given domains (all by default)
|
||||
|
@ -349,25 +344,21 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal
|
|||
|
||||
# Is it in Yunohost dmomain list?
|
||||
if domain not in yunohost.domain.domain_list(auth)['domains']:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_domain_unknown', domain=domain))
|
||||
raise YunohostError('certmanager_domain_unknown', domain=domain)
|
||||
|
||||
status = _get_status(domain)
|
||||
|
||||
# Does it expire soon?
|
||||
if status["validity"] > VALIDITY_LIMIT and not force:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_attempt_to_renew_valid_cert', domain=domain))
|
||||
raise YunohostError('certmanager_attempt_to_renew_valid_cert', domain=domain)
|
||||
|
||||
# Does it have a Let's Encrypt cert?
|
||||
if status["CA_type"]["code"] != "lets-encrypt":
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_attempt_to_renew_nonLE_cert', domain=domain))
|
||||
raise YunohostError('certmanager_attempt_to_renew_nonLE_cert', domain=domain)
|
||||
|
||||
# Check ACME challenge configured for given domain
|
||||
if not _check_acme_challenge_configuration(domain):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_acme_not_configured_for_domain', domain=domain))
|
||||
raise YunohostError('certmanager_acme_not_configured_for_domain', domain=domain)
|
||||
|
||||
if staging:
|
||||
logger.warning(
|
||||
|
@ -377,8 +368,8 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal
|
|||
for domain in domain_list:
|
||||
|
||||
operation_logger = OperationLogger('letsencrypt_cert_renew', [('domain', domain)],
|
||||
args={'force': force, 'no_checks': no_checks,
|
||||
'staging': staging, 'email': email})
|
||||
args={'force': force, 'no_checks': no_checks,
|
||||
'staging': staging, 'email': email})
|
||||
|
||||
logger.info(
|
||||
"Now attempting renewing of certificate for domain %s !", domain)
|
||||
|
@ -411,9 +402,10 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal
|
|||
logger.error("Sending email with details to root ...")
|
||||
_email_renewing_failed(domain, e, stack.getvalue())
|
||||
|
||||
###############################################################################
|
||||
# Back-end stuff #
|
||||
###############################################################################
|
||||
#
|
||||
# Back-end stuff #
|
||||
#
|
||||
|
||||
|
||||
def _install_cron():
|
||||
cron_job_file = "/etc/cron.daily/yunohost-certificate-renew"
|
||||
|
@ -422,7 +414,7 @@ def _install_cron():
|
|||
f.write("#!/bin/bash\n")
|
||||
f.write("yunohost domain cert-renew --email\n")
|
||||
|
||||
_set_permissions(cron_job_file, "root", "root", 0755)
|
||||
_set_permissions(cron_job_file, "root", "root", 0o755)
|
||||
|
||||
|
||||
def _email_renewing_failed(domain, exception_message, stack):
|
||||
|
@ -466,7 +458,7 @@ def _configure_for_acme_challenge(auth, domain):
|
|||
nginx_conf_file = "%s/000-acmechallenge.conf" % nginx_conf_folder
|
||||
|
||||
nginx_configuration = '''
|
||||
location ^~ '/.well-known/acme-challenge'
|
||||
location ^~ '/.well-known/acme-challenge/'
|
||||
{
|
||||
default_type "text/plain";
|
||||
alias %s;
|
||||
|
@ -484,8 +476,7 @@ location ^~ '/.well-known/acme-challenge'
|
|||
contents = f.read()
|
||||
|
||||
if '/.well-known/acme-challenge' in contents:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_conflicting_nginx_file', filepath=path))
|
||||
raise YunohostError('certmanager_conflicting_nginx_file', filepath=path)
|
||||
|
||||
# Write the conf
|
||||
if os.path.exists(nginx_conf_file):
|
||||
|
@ -528,8 +519,8 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False):
|
|||
if not os.path.exists(TMP_FOLDER):
|
||||
os.makedirs(TMP_FOLDER)
|
||||
|
||||
_set_permissions(WEBROOT_FOLDER, "root", "www-data", 0650)
|
||||
_set_permissions(TMP_FOLDER, "root", "root", 0640)
|
||||
_set_permissions(WEBROOT_FOLDER, "root", "www-data", 0o650)
|
||||
_set_permissions(TMP_FOLDER, "root", "root", 0o640)
|
||||
|
||||
# Regen conf for dnsmasq if needed
|
||||
_regen_dnsmasq_if_needed()
|
||||
|
@ -540,7 +531,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False):
|
|||
|
||||
domain_key_file = "%s/%s.pem" % (TMP_FOLDER, domain)
|
||||
_generate_key(domain_key_file)
|
||||
_set_permissions(domain_key_file, "root", "ssl-cert", 0640)
|
||||
_set_permissions(domain_key_file, "root", "ssl-cert", 0o640)
|
||||
|
||||
_prepare_certificate_signing_request(domain, domain_key_file, TMP_FOLDER)
|
||||
|
||||
|
@ -563,31 +554,28 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False):
|
|||
CA=certification_authority)
|
||||
except ValueError as e:
|
||||
if "urn:acme:error:rateLimited" in str(e):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_hit_rate_limit', domain=domain))
|
||||
raise YunohostError('certmanager_hit_rate_limit', domain=domain)
|
||||
else:
|
||||
logger.error(str(e))
|
||||
_display_debug_information(domain)
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_cert_signing_failed'))
|
||||
raise YunohostError('certmanager_cert_signing_failed')
|
||||
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_cert_signing_failed'))
|
||||
raise YunohostError('certmanager_cert_signing_failed')
|
||||
|
||||
import requests # lazy loading this module for performance reasons
|
||||
import requests # lazy loading this module for performance reasons
|
||||
try:
|
||||
intermediate_certificate = requests.get(INTERMEDIATE_CERTIFICATE_URL, timeout=30).text
|
||||
except requests.exceptions.Timeout as e:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_couldnt_fetch_intermediate_cert'))
|
||||
raise YunohostError('certmanager_couldnt_fetch_intermediate_cert')
|
||||
|
||||
# Now save the key and signed certificate
|
||||
logger.debug("Saving the key and signed certificate...")
|
||||
|
||||
# Create corresponding directory
|
||||
date_tag = datetime.now().strftime("%Y%m%d.%H%M%S")
|
||||
date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S")
|
||||
|
||||
if staging:
|
||||
folder_flag = "staging"
|
||||
|
@ -599,12 +587,12 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False):
|
|||
|
||||
os.makedirs(new_cert_folder)
|
||||
|
||||
_set_permissions(new_cert_folder, "root", "root", 0655)
|
||||
_set_permissions(new_cert_folder, "root", "root", 0o655)
|
||||
|
||||
# Move the private key
|
||||
domain_key_file_finaldest = os.path.join(new_cert_folder, "key.pem")
|
||||
shutil.move(domain_key_file, domain_key_file_finaldest)
|
||||
_set_permissions(domain_key_file_finaldest, "root", "ssl-cert", 0640)
|
||||
_set_permissions(domain_key_file_finaldest, "root", "ssl-cert", 0o640)
|
||||
|
||||
# Write the cert
|
||||
domain_cert_file = os.path.join(new_cert_folder, "crt.pem")
|
||||
|
@ -613,7 +601,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False):
|
|||
f.write(signed_certificate)
|
||||
f.write(intermediate_certificate)
|
||||
|
||||
_set_permissions(domain_cert_file, "root", "ssl-cert", 0640)
|
||||
_set_permissions(domain_cert_file, "root", "ssl-cert", 0o640)
|
||||
|
||||
if staging:
|
||||
return
|
||||
|
@ -624,12 +612,11 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False):
|
|||
status_summary = _get_status(domain)["summary"]
|
||||
|
||||
if status_summary["code"] != "great":
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_certificate_fetching_or_enabling_failed', domain=domain))
|
||||
raise YunohostError('certmanager_certificate_fetching_or_enabling_failed', domain=domain)
|
||||
|
||||
|
||||
def _prepare_certificate_signing_request(domain, key_file, output_folder):
|
||||
from OpenSSL import crypto # lazy loading this module for performance reasons
|
||||
from OpenSSL import crypto # lazy loading this module for performance reasons
|
||||
# Init a request
|
||||
csr = crypto.X509Req()
|
||||
|
||||
|
@ -658,23 +645,21 @@ def _get_status(domain):
|
|||
cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem")
|
||||
|
||||
if not os.path.isfile(cert_file):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_no_cert_file', domain=domain, file=cert_file))
|
||||
raise YunohostError('certmanager_no_cert_file', domain=domain, file=cert_file)
|
||||
|
||||
from OpenSSL import crypto # lazy loading this module for performance reasons
|
||||
from OpenSSL import crypto # lazy loading this module for performance reasons
|
||||
try:
|
||||
cert = crypto.load_certificate(
|
||||
crypto.FILETYPE_PEM, open(cert_file).read())
|
||||
except Exception as exception:
|
||||
import traceback
|
||||
traceback.print_exc(file=sys.stdout)
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_cannot_read_cert', domain=domain, file=cert_file, reason=exception))
|
||||
raise YunohostError('certmanager_cannot_read_cert', domain=domain, file=cert_file, reason=exception)
|
||||
|
||||
cert_subject = cert.get_subject().CN
|
||||
cert_issuer = cert.get_issuer().CN
|
||||
valid_up_to = datetime.strptime(cert.get_notAfter(), "%Y%m%d%H%M%SZ")
|
||||
days_remaining = (valid_up_to - datetime.now()).days
|
||||
days_remaining = (valid_up_to - datetime.utcnow()).days
|
||||
|
||||
if cert_issuer == _name_self_CA():
|
||||
CA_type = {
|
||||
|
@ -752,19 +737,19 @@ def _get_status(domain):
|
|||
"ACME_eligible": ACME_eligible
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Misc small stuff ... #
|
||||
###############################################################################
|
||||
#
|
||||
# Misc small stuff ... #
|
||||
#
|
||||
|
||||
|
||||
def _generate_account_key():
|
||||
logger.debug("Generating account key ...")
|
||||
_generate_key(ACCOUNT_KEY_FILE)
|
||||
_set_permissions(ACCOUNT_KEY_FILE, "root", "root", 0400)
|
||||
_set_permissions(ACCOUNT_KEY_FILE, "root", "root", 0o400)
|
||||
|
||||
|
||||
def _generate_key(destination_path):
|
||||
from OpenSSL import crypto # lazy loading this module for performance reasons
|
||||
from OpenSSL import crypto # lazy loading this module for performance reasons
|
||||
k = crypto.PKey()
|
||||
k.generate_key(crypto.TYPE_RSA, KEY_SIZE)
|
||||
|
||||
|
@ -803,15 +788,23 @@ def _enable_certificate(domain, new_cert_folder):
|
|||
for service in ("postfix", "dovecot", "metronome"):
|
||||
_run_service_command("restart", service)
|
||||
|
||||
if os.path.isfile('/etc/yunohost/installed'):
|
||||
# regen nginx conf to be sure it integrates OCSP Stapling
|
||||
# (We don't do this yet if postinstall is not finished yet)
|
||||
service_regen_conf(names=['nginx'])
|
||||
|
||||
_run_service_command("reload", "nginx")
|
||||
|
||||
from yunohost.hook import hook_callback
|
||||
hook_callback('post_cert_update', args=[domain])
|
||||
|
||||
|
||||
def _backup_current_cert(domain):
|
||||
logger.debug("Backuping existing certificate for domain %s", domain)
|
||||
|
||||
cert_folder_domain = os.path.join(CERT_FOLDER, domain)
|
||||
|
||||
date_tag = datetime.now().strftime("%Y%m%d.%H%M%S")
|
||||
date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S")
|
||||
backup_folder = "%s-backups/%s" % (cert_folder_domain, date_tag)
|
||||
|
||||
shutil.copytree(cert_folder_domain, backup_folder)
|
||||
|
@ -822,13 +815,11 @@ def _check_domain_is_ready_for_ACME(domain):
|
|||
|
||||
# Check if IP from DNS matches public IP
|
||||
if not _dns_ip_match_public_ip(public_ip, domain):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_domain_dns_ip_differs_from_public_ip', domain=domain))
|
||||
raise YunohostError('certmanager_domain_dns_ip_differs_from_public_ip', domain=domain)
|
||||
|
||||
# Check if domain seems to be accessible through HTTP?
|
||||
if not _domain_is_accessible_through_HTTP(public_ip, domain):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_domain_http_not_working', domain=domain))
|
||||
raise YunohostError('certmanager_domain_http_not_working', domain=domain)
|
||||
|
||||
|
||||
def _get_dns_ip(domain):
|
||||
|
@ -837,8 +828,7 @@ def _get_dns_ip(domain):
|
|||
resolver.nameservers = DNS_RESOLVERS
|
||||
answers = resolver.query(domain, "A")
|
||||
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'certmanager_error_no_A_record', domain=domain))
|
||||
raise YunohostError('certmanager_error_no_A_record', domain=domain)
|
||||
|
||||
return str(answers[0])
|
||||
|
||||
|
@ -848,7 +838,7 @@ def _dns_ip_match_public_ip(public_ip, domain):
|
|||
|
||||
|
||||
def _domain_is_accessible_through_HTTP(ip, domain):
|
||||
import requests # lazy loading this module for performance reasons
|
||||
import requests # lazy loading this module for performance reasons
|
||||
try:
|
||||
requests.head("http://" + ip, headers={"Host": domain}, timeout=10)
|
||||
except requests.exceptions.Timeout as e:
|
||||
|
|
|
@ -3,7 +3,9 @@ import glob
|
|||
from yunohost.tools import Migration
|
||||
from moulinette.utils.filesystem import chown
|
||||
|
||||
|
||||
class MyMigration(Migration):
|
||||
|
||||
"Change certificates group permissions from 'metronome' to 'ssl-cert'"
|
||||
|
||||
all_certificate_files = glob.glob("/etc/yunohost/certs/*/*.pem")
|
||||
|
|
|
@ -4,10 +4,9 @@ import requests
|
|||
import base64
|
||||
import time
|
||||
import json
|
||||
import errno
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
from yunohost.tools import Migration
|
||||
|
@ -17,6 +16,7 @@ logger = getActionLogger('yunohost.migration')
|
|||
|
||||
|
||||
class MyMigration(Migration):
|
||||
|
||||
"Migrate Dyndns stuff from MD5 TSIG to SHA512 TSIG"
|
||||
|
||||
def backward(self):
|
||||
|
@ -29,7 +29,7 @@ class MyMigration(Migration):
|
|||
try:
|
||||
(domain, private_key_path) = _guess_current_dyndns_domain(dyn_host)
|
||||
assert "+157" in private_key_path
|
||||
except (MoulinetteError, AssertionError):
|
||||
except (YunohostError, AssertionError):
|
||||
logger.info(m18n.n("migrate_tsig_not_needed"))
|
||||
return
|
||||
|
||||
|
@ -52,7 +52,7 @@ class MyMigration(Migration):
|
|||
'public_key_sha512': base64.b64encode(public_key_sha512),
|
||||
}, timeout=30)
|
||||
except requests.ConnectionError:
|
||||
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
|
||||
raise YunohostError('no_internet_connection')
|
||||
|
||||
if r.status_code != 201:
|
||||
try:
|
||||
|
@ -70,8 +70,8 @@ class MyMigration(Migration):
|
|||
# Migration didn't succeed, so we rollback and raise an exception
|
||||
os.system("mv /etc/yunohost/dyndns/*+165* /tmp")
|
||||
|
||||
raise MoulinetteError(m18n.n('migrate_tsig_failed', domain=domain,
|
||||
error_code=str(r.status_code), error=error))
|
||||
raise YunohostError('migrate_tsig_failed', domain=domain,
|
||||
error_code=str(r.status_code), error=error)
|
||||
|
||||
# remove old certificates
|
||||
os.system("mv /etc/yunohost/dyndns/*+157* /tmp")
|
||||
|
@ -88,4 +88,3 @@ class MyMigration(Migration):
|
|||
|
||||
logger.info(m18n.n('migrate_tsig_end'))
|
||||
return
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import os
|
|||
from shutil import copy2
|
||||
|
||||
from moulinette import m18n, msettings
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.process import check_output, call_async_output
|
||||
from moulinette.utils.filesystem import read_file
|
||||
|
@ -24,13 +24,14 @@ YUNOHOST_PACKAGES = ["yunohost", "yunohost-admin", "moulinette", "ssowat"]
|
|||
|
||||
|
||||
class MyMigration(Migration):
|
||||
|
||||
"Upgrade the system to Debian Stretch and Yunohost 3.0"
|
||||
|
||||
mode = "manual"
|
||||
|
||||
def backward(self):
|
||||
|
||||
raise MoulinetteError(m18n.n("migration_0003_backward_impossible"))
|
||||
raise YunohostError("migration_0003_backward_impossible")
|
||||
|
||||
def migrate(self):
|
||||
|
||||
|
@ -57,7 +58,7 @@ class MyMigration(Migration):
|
|||
self.apt_dist_upgrade(conf_flags=["old", "miss", "def"])
|
||||
_run_service_command("start", "mysql")
|
||||
if self.debian_major_version() == 8:
|
||||
raise MoulinetteError(m18n.n("migration_0003_still_on_jessie_after_main_upgrade", log=self.logfile))
|
||||
raise YunohostError("migration_0003_still_on_jessie_after_main_upgrade", log=self.logfile)
|
||||
|
||||
# Specific upgrade for fail2ban...
|
||||
logger.info(m18n.n("migration_0003_fail2ban_upgrade"))
|
||||
|
@ -107,11 +108,11 @@ class MyMigration(Migration):
|
|||
# would still be in 2.x...
|
||||
if not self.debian_major_version() == 8 \
|
||||
and not self.yunohost_major_version() == 2:
|
||||
raise MoulinetteError(m18n.n("migration_0003_not_jessie"))
|
||||
raise YunohostError("migration_0003_not_jessie")
|
||||
|
||||
# Have > 1 Go free space on /var/ ?
|
||||
if free_space_in_directory("/var/") / (1024**3) < 1.0:
|
||||
raise MoulinetteError(m18n.n("migration_0003_not_enough_free_space"))
|
||||
raise YunohostError("migration_0003_not_enough_free_space")
|
||||
|
||||
# Check system is up to date
|
||||
# (but we don't if 'stretch' is already in the sources.list ...
|
||||
|
@ -120,7 +121,7 @@ class MyMigration(Migration):
|
|||
self.apt_update()
|
||||
apt_list_upgradable = check_output("apt list --upgradable -a")
|
||||
if "upgradable" in apt_list_upgradable:
|
||||
raise MoulinetteError(m18n.n("migration_0003_system_not_fully_up_to_date"))
|
||||
raise YunohostError("migration_0003_system_not_fully_up_to_date")
|
||||
|
||||
@property
|
||||
def disclaimer(self):
|
||||
|
@ -168,11 +169,11 @@ class MyMigration(Migration):
|
|||
# - switch yunohost's repo to forge
|
||||
for f in sources_list:
|
||||
command = "sed -i -e 's@ jessie @ stretch @g' " \
|
||||
"-e '/backports/ s@^#*@#@' " \
|
||||
"-e 's@ jessie/updates @ stretch/updates @g' " \
|
||||
"-e 's@ jessie-updates @ stretch-updates @g' " \
|
||||
"-e 's@repo.yunohost@forge.yunohost@g' " \
|
||||
"{}".format(f)
|
||||
"-e '/backports/ s@^#*@#@' " \
|
||||
"-e 's@ jessie/updates @ stretch/updates @g' " \
|
||||
"-e 's@ jessie-updates @ stretch-updates @g' " \
|
||||
"-e 's@repo.yunohost@forge.yunohost@g' " \
|
||||
"{}".format(f)
|
||||
os.system(command)
|
||||
|
||||
def get_apps_equivs_packages(self):
|
||||
|
@ -286,7 +287,7 @@ class MyMigration(Migration):
|
|||
# Create tmp directory if it does not exists
|
||||
tmp_dir = os.path.join("/tmp/", self.name)
|
||||
if not os.path.exists(tmp_dir):
|
||||
os.mkdir(tmp_dir, 0700)
|
||||
os.mkdir(tmp_dir, 0o700)
|
||||
|
||||
for f in self.files_to_keep:
|
||||
dest_file = f.strip('/').replace("/", "_")
|
||||
|
|
|
@ -19,6 +19,7 @@ MIGRATION_COMMENT = "; YunoHost note : this file was automatically moved from {}
|
|||
|
||||
|
||||
class MyMigration(Migration):
|
||||
|
||||
"Migrate php5-fpm 'pool' conf files to php7 stuff"
|
||||
|
||||
def migrate(self):
|
||||
|
@ -58,7 +59,7 @@ class MyMigration(Migration):
|
|||
_run_service_command("enable", "php7.0-fpm")
|
||||
os.system("systemctl stop php5-fpm")
|
||||
os.system("systemctl disable php5-fpm")
|
||||
os.system("rm /etc/logrotate.d/php5-fpm") # We remove this otherwise the logrotate cron will be unhappy
|
||||
os.system("rm /etc/logrotate.d/php5-fpm") # We remove this otherwise the logrotate cron will be unhappy
|
||||
|
||||
# Get list of nginx conf file
|
||||
nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/*.conf")
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import subprocess
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
from yunohost.tools import Migration
|
||||
|
@ -11,6 +11,7 @@ logger = getActionLogger('yunohost.migration')
|
|||
|
||||
|
||||
class MyMigration(Migration):
|
||||
|
||||
"Migrate DBs from Postgresql 9.4 to 9.6 after migrating to Stretch"
|
||||
|
||||
def migrate(self):
|
||||
|
@ -20,10 +21,10 @@ class MyMigration(Migration):
|
|||
return
|
||||
|
||||
if not self.package_is_installed("postgresql-9.6"):
|
||||
raise MoulinetteError(m18n.n("migration_0005_postgresql_96_not_installed"))
|
||||
raise YunohostError("migration_0005_postgresql_96_not_installed")
|
||||
|
||||
if not space_used_by_directory("/var/lib/postgresql/9.4") > free_space_in_directory("/var/lib/postgresql"):
|
||||
raise MoulinetteError(m18n.n("migration_0005_not_enough_space", path="/var/lib/postgresql/"))
|
||||
raise YunohostError("migration_0005_not_enough_space", path="/var/lib/postgresql/")
|
||||
|
||||
subprocess.check_call("service postgresql stop", shell=True)
|
||||
subprocess.check_call("pg_dropcluster --stop 9.6 main", shell=True)
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
import spwd
|
||||
import crypt
|
||||
import random
|
||||
import string
|
||||
import subprocess
|
||||
|
||||
from moulinette import m18n
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.process import run_commands, check_output
|
||||
from moulinette.utils.filesystem import append_to_file
|
||||
from moulinette.authenticators.ldap import Authenticator
|
||||
from yunohost.tools import Migration
|
||||
|
||||
logger = getActionLogger('yunohost.migration')
|
||||
SMALL_PWD_LIST = ["yunohost", "olinuxino", "olinux", "raspberry", "admin", "root", "test", "rpi"]
|
||||
|
||||
|
||||
class MyMigration(Migration):
|
||||
|
||||
"Synchronize admin and root passwords"
|
||||
|
||||
def migrate(self):
|
||||
|
||||
new_hash = self._get_admin_hash()
|
||||
self._replace_root_hash(new_hash)
|
||||
|
||||
logger.info(m18n.n("root_password_replaced_by_admin_password"))
|
||||
|
||||
def backward(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
|
||||
# If the root password is still a "default" value,
|
||||
# then this is an emergency and migration shall
|
||||
# be applied automatically
|
||||
#
|
||||
# Otherwise, as playing with root password is touchy,
|
||||
# we set this as a manual migration.
|
||||
return "auto" if self._is_root_pwd_listed(SMALL_PWD_LIST) else "manual"
|
||||
|
||||
@property
|
||||
def disclaimer(self):
|
||||
if self._is_root_pwd_listed(SMALL_PWD_LIST):
|
||||
return None
|
||||
|
||||
return m18n.n("migration_0006_disclaimer")
|
||||
|
||||
def _get_admin_hash(self):
|
||||
"""
|
||||
Fetch the admin hash from the LDAP db using slapcat
|
||||
"""
|
||||
admin_hash = check_output("slapcat \
|
||||
| grep 'dn: cn=admin,dc=yunohost,dc=org' -A20 \
|
||||
| grep userPassword -A2 \
|
||||
| tr -d '\n ' \
|
||||
| tr ':' ' ' \
|
||||
| awk '{print $2}' \
|
||||
| base64 -d \
|
||||
| sed 's/{CRYPT}//g'")
|
||||
return admin_hash
|
||||
|
||||
def _replace_root_hash(self, new_hash):
|
||||
hash_root = spwd.getspnam("root").sp_pwd
|
||||
|
||||
with open('/etc/shadow', 'r') as before_file:
|
||||
before = before_file.read()
|
||||
|
||||
with open('/etc/shadow', 'w') as after_file:
|
||||
after_file.write(before.replace("root:" + hash_root,
|
||||
"root:" + new_hash))
|
||||
|
||||
def _is_root_pwd_listed(self, pwd_list):
|
||||
hash_root = spwd.getspnam("root").sp_pwd
|
||||
|
||||
for password in pwd_list:
|
||||
if hash_root == crypt.crypt(password, hash_root):
|
||||
return True
|
||||
return False
|
|
@ -0,0 +1,80 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
from shutil import copyfile
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.filesystem import mkdir, rm
|
||||
|
||||
from yunohost.tools import Migration
|
||||
from yunohost.service import service_regen_conf, \
|
||||
_get_conf_hashes, \
|
||||
_calculate_hash, \
|
||||
_run_service_command
|
||||
from yunohost.settings import settings_set
|
||||
from yunohost.utils.error import YunohostError
|
||||
|
||||
logger = getActionLogger('yunohost.migration')
|
||||
|
||||
SSHD_CONF = '/etc/ssh/sshd_config'
|
||||
|
||||
|
||||
class MyMigration(Migration):
|
||||
|
||||
"""
|
||||
This is the first step of a couple of migrations that ensure SSH conf is
|
||||
managed by YunoHost (even if the "from_script" flag is present, which was
|
||||
previously preventing it from being managed by YunoHost)
|
||||
|
||||
The goal of this first (automatic) migration is to make sure that the
|
||||
sshd_config is managed by the regen-conf mechanism.
|
||||
|
||||
If the from_script flag exists, then we keep the current SSH conf such that it
|
||||
will appear as "manually modified" to the regenconf.
|
||||
|
||||
In step 2 (manual), the admin will be able to choose wether or not to actually
|
||||
use the recommended configuration, with an appropriate disclaimer.
|
||||
"""
|
||||
|
||||
def migrate(self):
|
||||
|
||||
# Check if deprecated DSA Host Key is in config
|
||||
dsa_rgx = r'^[ \t]*HostKey[ \t]+/etc/ssh/ssh_host_dsa_key[ \t]*(?:#.*)?$'
|
||||
dsa = False
|
||||
for line in open(SSHD_CONF):
|
||||
if re.match(dsa_rgx, line) is not None:
|
||||
dsa = True
|
||||
break
|
||||
if dsa:
|
||||
settings_set("service.ssh.allow_deprecated_dsa_hostkey", True)
|
||||
|
||||
# Create sshd_config.d dir
|
||||
if not os.path.exists(SSHD_CONF + '.d'):
|
||||
mkdir(SSHD_CONF + '.d', 0o755, uid='root', gid='root')
|
||||
|
||||
# Here, we make it so that /etc/ssh/sshd_config is managed
|
||||
# by the regen conf (in particular in the case where the
|
||||
# from_script flag is present - in which case it was *not*
|
||||
# managed by the regenconf)
|
||||
# But because we can't be sure the user wants to use the
|
||||
# recommended conf, we backup then restore the /etc/ssh/sshd_config
|
||||
# right after the regenconf, such that it will appear as
|
||||
# "manually modified".
|
||||
if os.path.exists('/etc/yunohost/from_script'):
|
||||
rm('/etc/yunohost/from_script')
|
||||
copyfile(SSHD_CONF, '/etc/ssh/sshd_config.bkp')
|
||||
service_regen_conf(names=['ssh'], force=True)
|
||||
copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF)
|
||||
|
||||
# Restart ssh and backward if it fail
|
||||
if not _run_service_command('restart', 'ssh'):
|
||||
self.backward()
|
||||
raise YunohostError("migration_0007_cancel")
|
||||
|
||||
def backward(self):
|
||||
|
||||
# We don't backward completely but it should be enough
|
||||
copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF)
|
||||
if not _run_service_command('restart', 'ssh'):
|
||||
raise YunohostError("migration_0007_cannot_restart")
|
|
@ -0,0 +1,99 @@
|
|||
import re
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
from yunohost.tools import Migration
|
||||
from yunohost.service import service_regen_conf, \
|
||||
_get_conf_hashes, \
|
||||
_calculate_hash
|
||||
from yunohost.settings import settings_set, settings_get
|
||||
from yunohost.utils.error import YunohostError
|
||||
|
||||
logger = getActionLogger('yunohost.migration')
|
||||
|
||||
SSHD_CONF = '/etc/ssh/sshd_config'
|
||||
|
||||
|
||||
class MyMigration(Migration):
|
||||
|
||||
"""
|
||||
In this second step, the admin is asked if it's okay to use
|
||||
the recommended SSH configuration - which also implies
|
||||
disabling deprecated DSA key.
|
||||
|
||||
This has important implications in the way the user may connect
|
||||
to its server (key change, and a spooky warning might be given
|
||||
by SSH later)
|
||||
|
||||
A disclaimer explaining the various things to be aware of is
|
||||
shown - and the user may also choose to skip this migration.
|
||||
"""
|
||||
|
||||
def migrate(self):
|
||||
settings_set("service.ssh.allow_deprecated_dsa_hostkey", False)
|
||||
service_regen_conf(names=['ssh'], force=True)
|
||||
|
||||
def backward(self):
|
||||
|
||||
raise YunohostError("migration_0008_backward_impossible")
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
|
||||
# If the conf is already up to date
|
||||
# and no DSA key is used, then we're good to go
|
||||
# and the migration can be done automatically
|
||||
# (basically nothing shall change)
|
||||
ynh_hash = _get_conf_hashes('ssh').get(SSHD_CONF, None)
|
||||
current_hash = _calculate_hash(SSHD_CONF)
|
||||
dsa = settings_get("service.ssh.allow_deprecated_dsa_hostkey")
|
||||
if ynh_hash == current_hash and not dsa:
|
||||
return "auto"
|
||||
|
||||
return "manual"
|
||||
|
||||
@property
|
||||
def disclaimer(self):
|
||||
|
||||
if self.mode == "auto":
|
||||
return None
|
||||
|
||||
# Detect key things to be aware of before enabling the
|
||||
# recommended configuration
|
||||
dsa_key_enabled = False
|
||||
ports = []
|
||||
root_login = []
|
||||
port_rgx = r'^[ \t]*Port[ \t]+(\d+)[ \t]*(?:#.*)?$'
|
||||
root_rgx = r'^[ \t]*PermitRootLogin[ \t]([^# \t]*)[ \t]*(?:#.*)?$'
|
||||
dsa_rgx = r'^[ \t]*HostKey[ \t]+/etc/ssh/ssh_host_dsa_key[ \t]*(?:#.*)?$'
|
||||
for line in open(SSHD_CONF):
|
||||
|
||||
ports = ports + re.findall(port_rgx, line)
|
||||
|
||||
root_login = root_login + re.findall(root_rgx, line)
|
||||
|
||||
if not dsa_key_enabled and re.match(dsa_rgx, line) is not None:
|
||||
dsa_key_enabled = True
|
||||
|
||||
custom_port = ports != ['22'] and ports != []
|
||||
root_login_enabled = root_login and root_login[-1] != 'no'
|
||||
|
||||
# Build message
|
||||
message = m18n.n("migration_0008_general_disclaimer")
|
||||
|
||||
if custom_port:
|
||||
message += "\n\n" + m18n.n("migration_0008_port")
|
||||
|
||||
if root_login_enabled:
|
||||
message += "\n\n" + m18n.n("migration_0008_root")
|
||||
|
||||
if dsa_key_enabled:
|
||||
message += "\n\n" + m18n.n("migration_0008_dsa")
|
||||
|
||||
if custom_port or root_login_enabled or dsa_key_enabled:
|
||||
message += "\n\n" + m18n.n("migration_0008_warning")
|
||||
else:
|
||||
message += "\n\n" + m18n.n("migration_0008_no_warning")
|
||||
|
||||
return message
|
|
@ -25,12 +25,11 @@
|
|||
"""
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import yaml
|
||||
import errno
|
||||
|
||||
from moulinette import m18n, msettings
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
import yunohost.certificate
|
||||
|
@ -78,7 +77,7 @@ def domain_add(operation_logger, auth, domain, dyndns=False):
|
|||
try:
|
||||
auth.validate_uniqueness({'virtualdomain': domain})
|
||||
except MoulinetteError:
|
||||
raise MoulinetteError(errno.EEXIST, m18n.n('domain_exists'))
|
||||
raise YunohostError('domain_exists')
|
||||
|
||||
operation_logger.start()
|
||||
|
||||
|
@ -87,16 +86,14 @@ def domain_add(operation_logger, auth, domain, dyndns=False):
|
|||
|
||||
# Do not allow to subscribe to multiple dyndns domains...
|
||||
if os.path.exists('/etc/cron.d/yunohost-dyndns'):
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('domain_dyndns_already_subscribed'))
|
||||
raise YunohostError('domain_dyndns_already_subscribed')
|
||||
|
||||
from yunohost.dyndns import dyndns_subscribe, _dyndns_provides
|
||||
|
||||
# Check that this domain can effectively be provided by
|
||||
# dyndns.yunohost.org. (i.e. is it a nohost.me / noho.st)
|
||||
if not _dyndns_provides("dyndns.yunohost.org", domain):
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('domain_dyndns_root_unknown'))
|
||||
raise YunohostError('domain_dyndns_root_unknown')
|
||||
|
||||
# Actually subscribe
|
||||
dyndns_subscribe(domain=domain)
|
||||
|
@ -110,23 +107,20 @@ def domain_add(operation_logger, auth, domain, dyndns=False):
|
|||
}
|
||||
|
||||
if not auth.add('virtualdomain=%s,ou=domains' % domain, attr_dict):
|
||||
raise MoulinetteError(errno.EIO, m18n.n('domain_creation_failed'))
|
||||
raise YunohostError('domain_creation_failed')
|
||||
|
||||
# Don't regen these conf if we're still in postinstall
|
||||
if os.path.exists('/etc/yunohost/installed'):
|
||||
service_regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix'])
|
||||
service_regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix', 'rspamd'])
|
||||
app_ssowatconf(auth)
|
||||
|
||||
except Exception, e:
|
||||
from sys import exc_info;
|
||||
t, v, tb = exc_info()
|
||||
|
||||
except Exception:
|
||||
# Force domain removal silently
|
||||
try:
|
||||
domain_remove(auth, domain, True)
|
||||
except:
|
||||
pass
|
||||
raise t, v, tb
|
||||
raise
|
||||
|
||||
hook_callback('post_domain_add', args=[domain])
|
||||
|
||||
|
@ -147,11 +141,11 @@ def domain_remove(operation_logger, auth, domain, force=False):
|
|||
from yunohost.app import app_ssowatconf
|
||||
|
||||
if not force and domain not in domain_list(auth)['domains']:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
|
||||
raise YunohostError('domain_unknown')
|
||||
|
||||
# Check domain is not the main domain
|
||||
if domain == _get_maindomain():
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('domain_cannot_remove_main'))
|
||||
raise YunohostError('domain_cannot_remove_main')
|
||||
|
||||
# Check if apps are installed on the domain
|
||||
for app in os.listdir('/etc/yunohost/apps/'):
|
||||
|
@ -162,14 +156,13 @@ def domain_remove(operation_logger, auth, domain, force=False):
|
|||
continue
|
||||
else:
|
||||
if app_domain == domain:
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('domain_uninstall_app_first'))
|
||||
raise YunohostError('domain_uninstall_app_first')
|
||||
|
||||
operation_logger.start()
|
||||
if auth.remove('virtualdomain=' + domain + ',ou=domains') or force:
|
||||
os.system('rm -rf /etc/yunohost/certs/%s' % domain)
|
||||
else:
|
||||
raise MoulinetteError(errno.EIO, m18n.n('domain_deletion_failed'))
|
||||
raise YunohostError('domain_deletion_failed')
|
||||
|
||||
service_regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix'])
|
||||
app_ssowatconf(auth)
|
||||
|
@ -209,6 +202,11 @@ def domain_dns_conf(domain, ttl=None):
|
|||
for record in dns_conf["mail"]:
|
||||
result += "\n{name} {ttl} IN {type} {value}".format(**record)
|
||||
|
||||
result += "\n\n"
|
||||
result += "; Extra"
|
||||
for record in dns_conf["extra"]:
|
||||
result += "\n{name} {ttl} IN {type} {value}".format(**record)
|
||||
|
||||
is_cli = True if msettings.get('interface') == 'cli' else False
|
||||
if is_cli:
|
||||
logger.info(m18n.n("domain_dns_conf_is_just_a_recommendation"))
|
||||
|
@ -241,7 +239,7 @@ def _get_conflicting_apps(auth, domain, path):
|
|||
|
||||
# Abort if domain is unknown
|
||||
if domain not in domain_list(auth)['domains']:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
|
||||
raise YunohostError('domain_unknown')
|
||||
|
||||
# This import cannot be put on top of file because it would create a
|
||||
# recursive import...
|
||||
|
@ -334,6 +332,9 @@ def _build_dns_conf(domain, ttl=3600):
|
|||
{"type": "TXT", "name": "mail._domainkey", "value": "\"v=DKIM1; k=rsa; p=some-super-long-key\"", "ttl": 3600},
|
||||
{"type": "TXT", "name": "_dmarc", "value": "\"v=DMARC1; p=none\"", "ttl": 3600}
|
||||
],
|
||||
"extra": [
|
||||
{"type": "CAA", "name": "@", "value": "128 issue \"letsencrypt.org\"", "ttl": 3600},
|
||||
],
|
||||
}
|
||||
"""
|
||||
|
||||
|
@ -387,10 +388,16 @@ def _build_dns_conf(domain, ttl=3600):
|
|||
["_dmarc", ttl, "TXT", '"v=DMARC1; p=none"'],
|
||||
]
|
||||
|
||||
# Extra
|
||||
extra = [
|
||||
["@", ttl, "CAA", '128 issue "letsencrypt.org"']
|
||||
]
|
||||
|
||||
return {
|
||||
"basic": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in basic],
|
||||
"xmpp": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in xmpp],
|
||||
"mail": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in mail],
|
||||
"extra": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in extra],
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -27,25 +27,23 @@ import os
|
|||
import re
|
||||
import json
|
||||
import glob
|
||||
import time
|
||||
import base64
|
||||
import errno
|
||||
import subprocess
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.filesystem import read_file, write_to_file, rm
|
||||
from moulinette.utils.filesystem import write_to_file
|
||||
from moulinette.utils.network import download_json
|
||||
from moulinette.utils.process import check_output
|
||||
|
||||
from yunohost.utils.error import YunohostError
|
||||
from yunohost.domain import _get_maindomain, _build_dns_conf
|
||||
from yunohost.utils.network import get_public_ip
|
||||
from yunohost.log import is_unit_operation
|
||||
|
||||
logger = getActionLogger('yunohost.dyndns')
|
||||
|
||||
OLD_IPV4_FILE = '/etc/yunohost/dyndns/old_ip'
|
||||
OLD_IPV6_FILE = '/etc/yunohost/dyndns/old_ipv6'
|
||||
DYNDNS_ZONE = '/etc/yunohost/dyndns/zone'
|
||||
|
||||
RE_DYNDNS_PRIVATE_KEY_MD5 = re.compile(
|
||||
|
@ -77,9 +75,7 @@ def _dyndns_provides(provider, domain):
|
|||
dyndomains = download_json('https://%s/domains' % provider, timeout=30)
|
||||
except MoulinetteError as e:
|
||||
logger.error(str(e))
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('dyndns_could_not_check_provide',
|
||||
domain=domain, provider=provider))
|
||||
raise YunohostError('dyndns_could_not_check_provide', domain=domain, provider=provider)
|
||||
|
||||
# Extract 'dyndomain' from 'domain', e.g. 'nohost.me' from 'foo.nohost.me'
|
||||
dyndomain = '.'.join(domain.split('.')[1:])
|
||||
|
@ -96,7 +92,7 @@ def _dyndns_available(provider, domain):
|
|||
domain -- The full domain that you'd like.. e.g. "foo.nohost.me"
|
||||
|
||||
Returns:
|
||||
True if the domain is avaible, False otherwise.
|
||||
True if the domain is available, False otherwise.
|
||||
"""
|
||||
logger.debug("Checking if domain %s is available on %s ..."
|
||||
% (domain, provider))
|
||||
|
@ -106,9 +102,8 @@ def _dyndns_available(provider, domain):
|
|||
expected_status_code=None)
|
||||
except MoulinetteError as e:
|
||||
logger.error(str(e))
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('dyndns_could_not_check_available',
|
||||
domain=domain, provider=provider))
|
||||
raise YunohostError('dyndns_could_not_check_available',
|
||||
domain=domain, provider=provider)
|
||||
|
||||
return r == u"Domain %s is available" % domain
|
||||
|
||||
|
@ -130,14 +125,11 @@ def dyndns_subscribe(operation_logger, subscribe_host="dyndns.yunohost.org", dom
|
|||
|
||||
# Verify if domain is provided by subscribe_host
|
||||
if not _dyndns_provides(subscribe_host, domain):
|
||||
raise MoulinetteError(errno.ENOENT,
|
||||
m18n.n('dyndns_domain_not_provided',
|
||||
domain=domain, provider=subscribe_host))
|
||||
raise YunohostError('dyndns_domain_not_provided', domain=domain, provider=subscribe_host)
|
||||
|
||||
# Verify if domain is available
|
||||
if not _dyndns_available(subscribe_host, domain):
|
||||
raise MoulinetteError(errno.ENOENT,
|
||||
m18n.n('dyndns_unavailable', domain=domain))
|
||||
raise YunohostError('dyndns_unavailable', domain=domain)
|
||||
|
||||
operation_logger.start()
|
||||
|
||||
|
@ -156,19 +148,18 @@ def dyndns_subscribe(operation_logger, subscribe_host="dyndns.yunohost.org", dom
|
|||
with open(key_file) as f:
|
||||
key = f.readline().strip().split(' ', 6)[-1]
|
||||
|
||||
import requests # lazy loading this module for performance reasons
|
||||
import requests # lazy loading this module for performance reasons
|
||||
# Send subscription
|
||||
try:
|
||||
r = requests.post('https://%s/key/%s?key_algo=hmac-sha512' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain}, timeout=30)
|
||||
except requests.ConnectionError:
|
||||
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
|
||||
raise YunohostError('no_internet_connection')
|
||||
if r.status_code != 201:
|
||||
try:
|
||||
error = json.loads(r.text)['error']
|
||||
except:
|
||||
error = "Server error, code: %s. (Message: \"%s\")" % (r.status_code, r.text)
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('dyndns_registration_failed', error=error))
|
||||
raise YunohostError('dyndns_registration_failed', error=error)
|
||||
|
||||
logger.success(m18n.n('dyndns_registered'))
|
||||
|
||||
|
@ -193,11 +184,48 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
|
|||
|
||||
old_ipv4, old_ipv6 = (None, None) # (default values)
|
||||
|
||||
if os.path.isfile(OLD_IPV4_FILE):
|
||||
old_ipv4 = read_file(OLD_IPV4_FILE).rstrip()
|
||||
# If domain is not given, try to guess it from keys available...
|
||||
if domain is None:
|
||||
(domain, key) = _guess_current_dyndns_domain(dyn_host)
|
||||
# If key is not given, pick the first file we find with the domain given
|
||||
else:
|
||||
if key is None:
|
||||
keys = glob.glob('/etc/yunohost/dyndns/K{0}.+*.private'.format(domain))
|
||||
|
||||
if os.path.isfile(OLD_IPV6_FILE):
|
||||
old_ipv6 = read_file(OLD_IPV6_FILE).rstrip()
|
||||
if not keys:
|
||||
raise YunohostError('dyndns_key_not_found')
|
||||
|
||||
key = keys[0]
|
||||
|
||||
# This mean that hmac-md5 is used
|
||||
# (Re?)Trigger the migration to sha256 and return immediately.
|
||||
# The actual update will be done in next run.
|
||||
if "+157" in key:
|
||||
from yunohost.tools import _get_migration_by_name
|
||||
migration = _get_migration_by_name("migrate_to_tsig_sha256")
|
||||
try:
|
||||
migration.migrate(dyn_host, domain, key)
|
||||
except Exception as e:
|
||||
logger.error(m18n.n('migrations_migration_has_failed',
|
||||
exception=e,
|
||||
number=migration.number,
|
||||
name=migration.name),
|
||||
exc_info=1)
|
||||
return
|
||||
|
||||
# Extract 'host', e.g. 'nohost.me' from 'foo.nohost.me'
|
||||
host = domain.split('.')[1:]
|
||||
host = '.'.join(host)
|
||||
|
||||
logger.debug("Building zone update file ...")
|
||||
|
||||
lines = [
|
||||
'server %s' % dyn_host,
|
||||
'zone %s' % host,
|
||||
]
|
||||
|
||||
old_ipv4 = check_output("dig @%s +short %s" % (dyn_host, domain)).strip() or None
|
||||
old_ipv6 = check_output("dig @%s +short aaaa %s" % (dyn_host, domain)).strip() or None
|
||||
|
||||
# Get current IPv4 and IPv6
|
||||
ipv4_ = get_public_ip()
|
||||
|
@ -217,52 +245,12 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
|
|||
logger.info("No updated needed.")
|
||||
return
|
||||
else:
|
||||
operation_logger.related_to.append(('domain', domain))
|
||||
operation_logger.start()
|
||||
logger.info("Updated needed, going on...")
|
||||
|
||||
# If domain is not given, try to guess it from keys available...
|
||||
if domain is None:
|
||||
(domain, key) = _guess_current_dyndns_domain(dyn_host)
|
||||
# If key is not given, pick the first file we find with the domain given
|
||||
else:
|
||||
if key is None:
|
||||
keys = glob.glob('/etc/yunohost/dyndns/K{0}.+*.private'.format(domain))
|
||||
|
||||
if not keys:
|
||||
raise MoulinetteError(errno.EIO, m18n.n('dyndns_key_not_found'))
|
||||
|
||||
key = keys[0]
|
||||
|
||||
operation_logger.related_to.append(('domain', domain))
|
||||
operation_logger.start()
|
||||
|
||||
# This mean that hmac-md5 is used
|
||||
# (Re?)Trigger the migration to sha256 and return immediately.
|
||||
# The actual update will be done in next run.
|
||||
if "+157" in key:
|
||||
from yunohost.tools import _get_migration_by_name
|
||||
migration = _get_migration_by_name("migrate_to_tsig_sha256")
|
||||
try:
|
||||
migration.migrate(dyn_host, domain, key)
|
||||
except Exception as e:
|
||||
logger.error(m18n.n('migrations_migration_has_failed',
|
||||
exception=e,
|
||||
number=migration.number,
|
||||
name=migration.name),
|
||||
exc_info=1)
|
||||
return
|
||||
|
||||
# Extract 'host', e.g. 'nohost.me' from 'foo.nohost.me'
|
||||
host = domain.split('.')[1:]
|
||||
host = '.'.join(host)
|
||||
|
||||
logger.debug("Building zone update file ...")
|
||||
|
||||
lines = [
|
||||
'server %s' % dyn_host,
|
||||
'zone %s' % host,
|
||||
]
|
||||
|
||||
dns_conf = _build_dns_conf(domain)
|
||||
del dns_conf["extra"] # Ignore records from the 'extra' category
|
||||
|
||||
# Delete the old records for all domain/subdomains
|
||||
|
||||
|
@ -283,7 +271,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
|
|||
# should be muc.the.domain.tld. or the.domain.tld
|
||||
if record["value"] == "@":
|
||||
record["value"] = domain
|
||||
record["value"] = record["value"].replace(";","\;")
|
||||
record["value"] = record["value"].replace(";", "\;")
|
||||
|
||||
action = "update add {name}.{domain}. {ttl} {type} {value}".format(domain=domain, **record)
|
||||
action = action.replace(" @.", " ")
|
||||
|
@ -304,18 +292,10 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
|
|||
command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE]
|
||||
subprocess.check_call(command)
|
||||
except subprocess.CalledProcessError:
|
||||
rm(OLD_IPV4_FILE, force=True) # Remove file (ignore if non-existent)
|
||||
rm(OLD_IPV6_FILE, force=True) # Remove file (ignore if non-existent)
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('dyndns_ip_update_failed'))
|
||||
raise YunohostError('dyndns_ip_update_failed')
|
||||
|
||||
logger.success(m18n.n('dyndns_ip_updated'))
|
||||
|
||||
if ipv4 is not None:
|
||||
write_to_file(OLD_IPV4_FILE, ipv4)
|
||||
if ipv6 is not None:
|
||||
write_to_file(OLD_IPV6_FILE, ipv6)
|
||||
|
||||
|
||||
def dyndns_installcron():
|
||||
"""
|
||||
|
@ -338,7 +318,7 @@ def dyndns_removecron():
|
|||
try:
|
||||
os.remove("/etc/cron.d/yunohost-dyndns")
|
||||
except:
|
||||
raise MoulinetteError(errno.EIO, m18n.n('dyndns_cron_remove_failed'))
|
||||
raise YunohostError('dyndns_cron_remove_failed')
|
||||
|
||||
logger.success(m18n.n('dyndns_cron_removed'))
|
||||
|
||||
|
@ -368,5 +348,4 @@ def _guess_current_dyndns_domain(dyn_host):
|
|||
else:
|
||||
return (_domain, path)
|
||||
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('dyndns_no_domain_registered'))
|
||||
raise YunohostError('dyndns_no_domain_registered')
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import errno
|
||||
try:
|
||||
import miniupnpc
|
||||
except ImportError:
|
||||
|
@ -34,7 +33,7 @@ except ImportError:
|
|||
sys.exit(1)
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils import process
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.text import prependlines
|
||||
|
@ -268,7 +267,7 @@ def firewall_reload(skip_upnp=False):
|
|||
reloaded = True
|
||||
|
||||
if not reloaded:
|
||||
raise MoulinetteError(errno.ESRCH, m18n.n('firewall_reload_failed'))
|
||||
raise YunohostError('firewall_reload_failed')
|
||||
|
||||
hook_callback('post_iptable_rules',
|
||||
args=[upnp, os.path.exists("/proc/net/if_inet6")])
|
||||
|
@ -338,12 +337,13 @@ def firewall_upnp(action='status', no_refresh=False):
|
|||
if action == 'status':
|
||||
no_refresh = True
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('action_invalid', action=action))
|
||||
raise YunohostError('action_invalid', action=action)
|
||||
|
||||
# Refresh port mapping using UPnP
|
||||
if not no_refresh:
|
||||
upnpc = miniupnpc.UPnP()
|
||||
upnpc.discoverdelay = 3000
|
||||
upnpc.discoverdelay = 62000
|
||||
upnpc.localport = 1900
|
||||
|
||||
# Discover UPnP device(s)
|
||||
logger.debug('discovering UPnP devices...')
|
||||
|
@ -374,10 +374,10 @@ def firewall_upnp(action='status', no_refresh=False):
|
|||
try:
|
||||
# Add new port mapping
|
||||
upnpc.addportmapping(port, protocol, upnpc.lanaddr,
|
||||
port, 'yunohost firewall: port %d' % port, '')
|
||||
port, 'yunohost firewall: port %d' % port, '')
|
||||
except:
|
||||
logger.debug('unable to add port %d using UPnP',
|
||||
port, exc_info=1)
|
||||
port, exc_info=1)
|
||||
enabled = False
|
||||
|
||||
if enabled != firewall['uPnP']['enabled']:
|
||||
|
@ -406,7 +406,7 @@ def firewall_upnp(action='status', no_refresh=False):
|
|||
firewall_reload(skip_upnp=True)
|
||||
|
||||
if action == 'enable' and not enabled:
|
||||
raise MoulinetteError(errno.ENXIO, m18n.n('upnp_port_open_failed'))
|
||||
raise YunohostError('upnp_port_open_failed')
|
||||
return {'enabled': enabled}
|
||||
|
||||
|
||||
|
@ -418,7 +418,7 @@ def firewall_stop():
|
|||
"""
|
||||
|
||||
if os.system("iptables -w -P INPUT ACCEPT") != 0:
|
||||
raise MoulinetteError(errno.ESRCH, m18n.n('iptables_unavailable'))
|
||||
raise YunohostError('iptables_unavailable')
|
||||
|
||||
os.system("iptables -w -F")
|
||||
os.system("iptables -w -X")
|
||||
|
|
|
@ -25,12 +25,11 @@
|
|||
"""
|
||||
import os
|
||||
import re
|
||||
import errno
|
||||
import tempfile
|
||||
from glob import iglob
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils import log
|
||||
|
||||
HOOK_FOLDER = '/usr/share/yunohost/hooks/'
|
||||
|
@ -112,7 +111,7 @@ def hook_info(action, name):
|
|||
})
|
||||
|
||||
if not hooks:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('hook_name_unknown', name=name))
|
||||
raise YunohostError('hook_name_unknown', name=name)
|
||||
return {
|
||||
'action': action,
|
||||
'name': name,
|
||||
|
@ -174,7 +173,7 @@ def hook_list(action, list_by='name', show_info=False):
|
|||
# Add only the name
|
||||
d.add(name)
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('hook_list_by_invalid'))
|
||||
raise YunohostError('hook_list_by_invalid')
|
||||
|
||||
def _append_folder(d, folder):
|
||||
# Iterate over and add hook from a folder
|
||||
|
@ -255,8 +254,7 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None,
|
|||
try:
|
||||
hl = hooks_names[n]
|
||||
except KeyError:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('hook_name_unknown', n))
|
||||
raise YunohostError('hook_name_unknown', n)
|
||||
# Iterate over hooks with this name
|
||||
for h in hl:
|
||||
# Update hooks dict
|
||||
|
@ -282,7 +280,7 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None,
|
|||
path=path, args=args)
|
||||
hook_exec(path, args=hook_args, chdir=chdir, env=env,
|
||||
no_trace=no_trace, raise_on_error=True)
|
||||
except MoulinetteError as e:
|
||||
except YunohostError as e:
|
||||
state = 'failed'
|
||||
logger.error(e.strerror, exc_info=1)
|
||||
post_callback(name=name, priority=priority, path=path,
|
||||
|
@ -319,7 +317,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
|
|||
if path[0] != '/':
|
||||
path = os.path.realpath(path)
|
||||
if not os.path.isfile(path):
|
||||
raise MoulinetteError(errno.EIO, m18n.g('file_not_exist', path=path))
|
||||
raise YunohostError('file_not_exist', path=path)
|
||||
|
||||
# Construct command variables
|
||||
cmd_args = ''
|
||||
|
@ -356,7 +354,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
|
|||
# prepend environment variables
|
||||
cmd = '{0} {1}'.format(
|
||||
' '.join(['{0}={1}'.format(k, shell_quote(v))
|
||||
for k, v in env.items()]), cmd)
|
||||
for k, v in env.items()]), cmd)
|
||||
command.append(cmd.format(script=cmd_script, args=cmd_args))
|
||||
|
||||
if logger.isEnabledFor(log.DEBUG):
|
||||
|
@ -371,8 +369,8 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
|
|||
)
|
||||
|
||||
if stdinfo:
|
||||
callbacks = ( callbacks[0], callbacks[1],
|
||||
lambda l: logger.info(l.rstrip()))
|
||||
callbacks = (callbacks[0], callbacks[1],
|
||||
lambda l: logger.info(l.rstrip()))
|
||||
|
||||
logger.debug("About to run the command '%s'" % command)
|
||||
|
||||
|
@ -384,14 +382,12 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
|
|||
# Check and return process' return code
|
||||
if returncode is None:
|
||||
if raise_on_error:
|
||||
raise MoulinetteError(
|
||||
errno.EIO, m18n.n('hook_exec_not_terminated', path=path))
|
||||
raise YunohostError('hook_exec_not_terminated', path=path)
|
||||
else:
|
||||
logger.error(m18n.n('hook_exec_not_terminated', path=path))
|
||||
return 1
|
||||
elif raise_on_error and returncode != 0:
|
||||
raise MoulinetteError(
|
||||
errno.EIO, m18n.n('hook_exec_failed', path=path))
|
||||
raise YunohostError('hook_exec_failed', path=path)
|
||||
return returncode
|
||||
|
||||
|
||||
|
|
|
@ -26,15 +26,13 @@
|
|||
|
||||
import os
|
||||
import yaml
|
||||
import errno
|
||||
import collections
|
||||
|
||||
from datetime import datetime
|
||||
from logging import FileHandler, getLogger, Formatter
|
||||
from sys import exc_info
|
||||
|
||||
from moulinette import m18n, msettings
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.filesystem import read_file
|
||||
|
||||
|
@ -148,8 +146,7 @@ def log_display(path, number=50, share=False):
|
|||
log_path = base_path + LOG_FILE_EXT
|
||||
|
||||
if not os.path.exists(md_path) and not os.path.exists(log_path):
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('log_does_exists', log=path))
|
||||
raise YunohostError('log_does_exists', log=path)
|
||||
|
||||
infos = {}
|
||||
|
||||
|
@ -189,7 +186,7 @@ def log_display(path, number=50, share=False):
|
|||
if os.path.exists(log_path):
|
||||
logger.warning(error)
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, error)
|
||||
raise YunohostError(error)
|
||||
|
||||
# Display logs if exist
|
||||
if os.path.exists(log_path):
|
||||
|
@ -285,6 +282,7 @@ def is_unit_operation(entities=['app', 'domain', 'service', 'user'],
|
|||
|
||||
|
||||
class OperationLogger(object):
|
||||
|
||||
"""
|
||||
Instances of this class represents unit operation done on the ynh instance.
|
||||
|
||||
|
@ -316,7 +314,7 @@ class OperationLogger(object):
|
|||
"""
|
||||
|
||||
if self.started_at is None:
|
||||
self.started_at = datetime.now()
|
||||
self.started_at = datetime.utcnow()
|
||||
self.flush()
|
||||
self._register_log()
|
||||
|
||||
|
@ -408,7 +406,7 @@ class OperationLogger(object):
|
|||
return
|
||||
if error is not None and not isinstance(error, basestring):
|
||||
error = str(error)
|
||||
self.ended_at = datetime.now()
|
||||
self.ended_at = datetime.utcnow()
|
||||
self._error = error
|
||||
self._success = error is None
|
||||
if self.logger is not None:
|
||||
|
@ -425,7 +423,7 @@ class OperationLogger(object):
|
|||
else:
|
||||
if is_api:
|
||||
msg = "<strong>" + m18n.n('log_link_to_failed_log',
|
||||
name=self.name, desc=desc) + "</strong>"
|
||||
name=self.name, desc=desc) + "</strong>"
|
||||
else:
|
||||
msg = m18n.n('log_help_to_get_failed_log', name=self.name,
|
||||
desc=desc)
|
||||
|
|
|
@ -31,14 +31,13 @@ import calendar
|
|||
import subprocess
|
||||
import xmlrpclib
|
||||
import os.path
|
||||
import errno
|
||||
import os
|
||||
import dns.resolver
|
||||
import cPickle as pickle
|
||||
from datetime import datetime
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
from yunohost.utils.network import get_public_ip
|
||||
|
@ -83,7 +82,7 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False):
|
|||
result_dname = dn
|
||||
if len(devices) == 0:
|
||||
if mountpoint is not None:
|
||||
raise MoulinetteError(errno.ENODEV, m18n.n('mountpoint_unknown'))
|
||||
raise YunohostError('mountpoint_unknown')
|
||||
return result
|
||||
|
||||
# Retrieve monitoring for unit(s)
|
||||
|
@ -141,7 +140,7 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False):
|
|||
for dname in devices_names:
|
||||
_set(dname, 'not-available')
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u))
|
||||
raise YunohostError('unit_unknown', unit=u)
|
||||
|
||||
if result_dname is not None:
|
||||
return result[result_dname]
|
||||
|
@ -237,7 +236,7 @@ def monitor_network(units=None, human_readable=False):
|
|||
'gateway': gateway,
|
||||
}
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u))
|
||||
raise YunohostError('unit_unknown', unit=u)
|
||||
|
||||
if len(units) == 1:
|
||||
return result[units[0]]
|
||||
|
@ -287,9 +286,9 @@ def monitor_system(units=None, human_readable=False):
|
|||
elif u == 'infos':
|
||||
result[u] = json.loads(glances.getSystem())
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u))
|
||||
raise YunohostError('unit_unknown', unit=u)
|
||||
|
||||
if len(units) == 1 and type(result[units[0]]) is not str:
|
||||
if len(units) == 1 and not isinstance(result[units[0]], str):
|
||||
return result[units[0]]
|
||||
return result
|
||||
|
||||
|
@ -303,7 +302,7 @@ def monitor_update_stats(period):
|
|||
|
||||
"""
|
||||
if period not in ['day', 'week', 'month']:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('monitor_period_invalid'))
|
||||
raise YunohostError('monitor_period_invalid')
|
||||
|
||||
stats = _retrieve_stats(period)
|
||||
if not stats:
|
||||
|
@ -321,7 +320,7 @@ def monitor_update_stats(period):
|
|||
else:
|
||||
monitor = _monitor_all(p, 0)
|
||||
if not monitor:
|
||||
raise MoulinetteError(errno.ENODATA, m18n.n('monitor_stats_no_update'))
|
||||
raise YunohostError('monitor_stats_no_update')
|
||||
|
||||
stats['timestamp'].append(time.time())
|
||||
|
||||
|
@ -386,15 +385,13 @@ def monitor_show_stats(period, date=None):
|
|||
|
||||
"""
|
||||
if period not in ['day', 'week', 'month']:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('monitor_period_invalid'))
|
||||
raise YunohostError('monitor_period_invalid')
|
||||
|
||||
result = _retrieve_stats(period, date)
|
||||
if result is False:
|
||||
raise MoulinetteError(errno.ENOENT,
|
||||
m18n.n('monitor_stats_file_not_found'))
|
||||
raise YunohostError('monitor_stats_file_not_found')
|
||||
elif result is None:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('monitor_stats_period_unavailable'))
|
||||
raise YunohostError('monitor_stats_period_unavailable')
|
||||
return result
|
||||
|
||||
|
||||
|
@ -407,7 +404,7 @@ def monitor_enable(with_stats=False):
|
|||
|
||||
"""
|
||||
from yunohost.service import (service_status, service_enable,
|
||||
service_start)
|
||||
service_start)
|
||||
|
||||
glances = service_status('glances')
|
||||
if glances['status'] != 'running':
|
||||
|
@ -417,7 +414,7 @@ def monitor_enable(with_stats=False):
|
|||
|
||||
# Install crontab
|
||||
if with_stats:
|
||||
# day: every 5 min # week: every 1 h # month: every 4 h #
|
||||
# day: every 5 min # week: every 1 h # month: every 4 h #
|
||||
rules = ('*/5 * * * * root {cmd} day >> /dev/null\n'
|
||||
'3 * * * * root {cmd} week >> /dev/null\n'
|
||||
'6 */4 * * * root {cmd} month >> /dev/null').format(
|
||||
|
@ -434,7 +431,7 @@ def monitor_disable():
|
|||
|
||||
"""
|
||||
from yunohost.service import (service_status, service_disable,
|
||||
service_stop)
|
||||
service_stop)
|
||||
|
||||
glances = service_status('glances')
|
||||
if glances['status'] != 'inactive':
|
||||
|
@ -442,7 +439,7 @@ def monitor_disable():
|
|||
if glances['loaded'] != 'disabled':
|
||||
try:
|
||||
service_disable('glances')
|
||||
except MoulinetteError as e:
|
||||
except YunohostError as e:
|
||||
logger.warning(e.strerror)
|
||||
|
||||
# Remove crontab
|
||||
|
@ -470,8 +467,8 @@ def _get_glances_api():
|
|||
from yunohost.service import service_status
|
||||
|
||||
if service_status('glances')['status'] != 'running':
|
||||
raise MoulinetteError(errno.EPERM, m18n.n('monitor_not_enabled'))
|
||||
raise MoulinetteError(errno.EIO, m18n.n('monitor_glances_con_failed'))
|
||||
raise YunohostError('monitor_not_enabled')
|
||||
raise YunohostError('monitor_glances_con_failed')
|
||||
|
||||
|
||||
def _extract_inet(string, skip_netmask=False, skip_loopback=True):
|
||||
|
|
|
@ -28,7 +28,6 @@ import time
|
|||
import yaml
|
||||
import json
|
||||
import subprocess
|
||||
import errno
|
||||
import shutil
|
||||
import hashlib
|
||||
|
||||
|
@ -36,11 +35,11 @@ from difflib import unified_diff
|
|||
from datetime import datetime
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils import log, filesystem
|
||||
|
||||
from yunohost.hook import hook_callback
|
||||
from yunohost.log import is_unit_operation
|
||||
from yunohost.hook import hook_callback, hook_list
|
||||
|
||||
BASE_CONF_PATH = '/home/yunohost.conf'
|
||||
BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup')
|
||||
|
@ -85,7 +84,7 @@ def service_add(name, status=None, log=None, runlevel=None, need_lock=False, des
|
|||
_save_services(services)
|
||||
except:
|
||||
# we'll get a logger.warning with more details in _save_services
|
||||
raise MoulinetteError(errno.EIO, m18n.n('service_add_failed', service=name))
|
||||
raise YunohostError('service_add_failed', service=name)
|
||||
|
||||
logger.success(m18n.n('service_added', service=name))
|
||||
|
||||
|
@ -103,13 +102,13 @@ def service_remove(name):
|
|||
try:
|
||||
del services[name]
|
||||
except KeyError:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=name))
|
||||
raise YunohostError('service_unknown', service=name)
|
||||
|
||||
try:
|
||||
_save_services(services)
|
||||
except:
|
||||
# we'll get a logger.warning with more details in _save_services
|
||||
raise MoulinetteError(errno.EIO, m18n.n('service_remove_failed', service=name))
|
||||
raise YunohostError('service_remove_failed', service=name)
|
||||
|
||||
logger.success(m18n.n('service_removed', service=name))
|
||||
|
||||
|
@ -130,10 +129,7 @@ def service_start(names):
|
|||
logger.success(m18n.n('service_started', service=name))
|
||||
else:
|
||||
if service_status(name)['status'] != 'running':
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_start_failed',
|
||||
service=name,
|
||||
logs=_get_journalctl_logs(name)))
|
||||
raise YunohostError('service_start_failed', service=name, logs=_get_journalctl_logs(name))
|
||||
logger.debug(m18n.n('service_already_started', service=name))
|
||||
|
||||
|
||||
|
@ -152,12 +148,10 @@ def service_stop(names):
|
|||
logger.success(m18n.n('service_stopped', service=name))
|
||||
else:
|
||||
if service_status(name)['status'] != 'inactive':
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_stop_failed',
|
||||
service=name,
|
||||
logs=_get_journalctl_logs(name)))
|
||||
raise YunohostError('service_stop_failed', service=name, logs=_get_journalctl_logs(name))
|
||||
logger.debug(m18n.n('service_already_stopped', service=name))
|
||||
|
||||
|
||||
@is_unit_operation()
|
||||
def service_enable(operation_logger, names):
|
||||
"""
|
||||
|
@ -174,10 +168,7 @@ def service_enable(operation_logger, names):
|
|||
if _run_service_command('enable', name):
|
||||
logger.success(m18n.n('service_enabled', service=name))
|
||||
else:
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_enable_failed',
|
||||
service=name,
|
||||
logs=_get_journalctl_logs(name)))
|
||||
raise YunohostError('service_enable_failed', service=name, logs=_get_journalctl_logs(name))
|
||||
|
||||
|
||||
def service_disable(names):
|
||||
|
@ -194,10 +185,7 @@ def service_disable(names):
|
|||
if _run_service_command('disable', name):
|
||||
logger.success(m18n.n('service_disabled', service=name))
|
||||
else:
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_disable_failed',
|
||||
service=name,
|
||||
logs=_get_journalctl_logs(name)))
|
||||
raise YunohostError('service_disable_failed', service=name, logs=_get_journalctl_logs(name))
|
||||
|
||||
|
||||
def service_status(names=[]):
|
||||
|
@ -220,8 +208,7 @@ def service_status(names=[]):
|
|||
|
||||
for name in names:
|
||||
if check_names and name not in services.keys():
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('service_unknown', service=name))
|
||||
raise YunohostError('service_unknown', service=name)
|
||||
|
||||
# this "service" isn't a service actually so we skip it
|
||||
#
|
||||
|
@ -248,10 +235,7 @@ def service_status(names=[]):
|
|||
'status': "unknown",
|
||||
'loaded': "unknown",
|
||||
'active': "unknown",
|
||||
'active_at': {
|
||||
"timestamp": "unknown",
|
||||
"human": "unknown",
|
||||
},
|
||||
'active_at': "unknown",
|
||||
'description': "Error: failed to get information for this service, it doesn't exists for systemd",
|
||||
'service_file_path': "unknown",
|
||||
}
|
||||
|
@ -273,13 +257,13 @@ def service_status(names=[]):
|
|||
'status': str(status.get("SubState", "unknown")),
|
||||
'loaded': "enabled" if str(status.get("LoadState", "unknown")) == "loaded" else str(status.get("LoadState", "unknown")),
|
||||
'active': str(status.get("ActiveState", "unknown")),
|
||||
'active_at': {
|
||||
"timestamp": str(status.get("ActiveEnterTimestamp", "unknown")),
|
||||
"human": datetime.fromtimestamp(status["ActiveEnterTimestamp"] / 1000000).strftime("%F %X") if "ActiveEnterTimestamp" in status else "unknown",
|
||||
},
|
||||
'description': description,
|
||||
'service_file_path': str(status.get("FragmentPath", "unknown")),
|
||||
}
|
||||
if "ActiveEnterTimestamp" in status:
|
||||
result[name]['active_at'] = datetime.utcfromtimestamp(status["ActiveEnterTimestamp"] / 1000000)
|
||||
else:
|
||||
result[name]['active_at'] = "unknown"
|
||||
|
||||
if len(names) == 1:
|
||||
return result[names[0]]
|
||||
|
@ -293,7 +277,7 @@ def _get_service_information_from_systemd(service):
|
|||
|
||||
d = dbus.SystemBus()
|
||||
|
||||
systemd = d.get_object('org.freedesktop.systemd1','/org/freedesktop/systemd1')
|
||||
systemd = d.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1')
|
||||
manager = dbus.Interface(systemd, 'org.freedesktop.systemd1.Manager')
|
||||
|
||||
try:
|
||||
|
@ -323,10 +307,10 @@ def service_log(name, number=50):
|
|||
services = _get_services()
|
||||
|
||||
if name not in services.keys():
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=name))
|
||||
raise YunohostError('service_unknown', service=name)
|
||||
|
||||
if 'log' not in services[name]:
|
||||
raise MoulinetteError(errno.EPERM, m18n.n('service_no_log', service=name))
|
||||
raise YunohostError('service_no_log', service=name)
|
||||
|
||||
log_list = services[name]['log']
|
||||
|
||||
|
@ -394,7 +378,7 @@ def service_regen_conf(operation_logger, names=[], with_diff=False, force=False,
|
|||
if not names:
|
||||
operation_logger.name_parameter_override = 'all'
|
||||
elif len(names) != 1:
|
||||
operation_logger.name_parameter_override = str(len(operation_logger.related_to))+'_services'
|
||||
operation_logger.name_parameter_override = str(len(operation_logger.related_to)) + '_services'
|
||||
operation_logger.start()
|
||||
|
||||
# Clean pending conf directory
|
||||
|
@ -406,7 +390,7 @@ def service_regen_conf(operation_logger, names=[], with_diff=False, force=False,
|
|||
shutil.rmtree(os.path.join(PENDING_CONF_DIR, name),
|
||||
ignore_errors=True)
|
||||
else:
|
||||
filesystem.mkdir(PENDING_CONF_DIR, 0755, True)
|
||||
filesystem.mkdir(PENDING_CONF_DIR, 0o755, True)
|
||||
|
||||
# Format common hooks arguments
|
||||
common_args = [1 if force else 0, 1 if dry_run else 0]
|
||||
|
@ -417,20 +401,25 @@ def service_regen_conf(operation_logger, names=[], with_diff=False, force=False,
|
|||
def _pre_call(name, priority, path, args):
|
||||
# create the pending conf directory for the service
|
||||
service_pending_path = os.path.join(PENDING_CONF_DIR, name)
|
||||
filesystem.mkdir(service_pending_path, 0755, True, uid='root')
|
||||
filesystem.mkdir(service_pending_path, 0o755, True, uid='root')
|
||||
|
||||
# return the arguments to pass to the script
|
||||
return pre_args + [service_pending_path, ]
|
||||
|
||||
# Don't regen SSH if not specifically specified
|
||||
if not names:
|
||||
names = hook_list('conf_regen', list_by='name',
|
||||
show_info=False)['hooks']
|
||||
names.remove('ssh')
|
||||
|
||||
pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call)
|
||||
|
||||
# Update the services name
|
||||
names = pre_result['succeed'].keys()
|
||||
|
||||
if not names:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('service_regenconf_failed',
|
||||
services=', '.join(pre_result['failed'])))
|
||||
raise YunohostError('service_regenconf_failed',
|
||||
services=', '.join(pre_result['failed']))
|
||||
|
||||
# Set the processing method
|
||||
_regen = _process_regen_conf if not dry_run else lambda *a, **k: True
|
||||
|
@ -502,8 +491,8 @@ def service_regen_conf(operation_logger, names=[], with_diff=False, force=False,
|
|||
# we assume that it is safe to regen it, since the file is backuped
|
||||
# anyway (by default in _regen), as long as we warn the user
|
||||
# appropriately.
|
||||
logger.info(m18n.n('service_conf_new_managed_file',
|
||||
conf=system_path, service=service))
|
||||
logger.info(m18n.n('service_conf_now_managed_by_yunohost',
|
||||
conf=system_path))
|
||||
regenerated = _regen(system_path, pending_path)
|
||||
conf_status = 'new'
|
||||
elif force:
|
||||
|
@ -606,7 +595,7 @@ def _run_service_command(action, service):
|
|||
"""
|
||||
services = _get_services()
|
||||
if service not in services.keys():
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=service))
|
||||
raise YunohostError('service_unknown', service=service)
|
||||
|
||||
possible_actions = ['start', 'stop', 'restart', 'reload', 'enable', 'disable']
|
||||
if action not in possible_actions:
|
||||
|
@ -615,7 +604,7 @@ def _run_service_command(action, service):
|
|||
cmd = 'systemctl %s %s' % (action, service)
|
||||
|
||||
need_lock = services[service].get('need_lock', False) \
|
||||
and action in ['start', 'stop', 'restart', 'reload']
|
||||
and action in ['start', 'stop', 'restart', 'reload']
|
||||
|
||||
try:
|
||||
# Launch the command
|
||||
|
@ -649,10 +638,10 @@ def _give_lock(action, service, p):
|
|||
else:
|
||||
systemctl_PID_name = "ControlPID"
|
||||
|
||||
cmd_get_son_PID ="systemctl show %s -p %s" % (service, systemctl_PID_name)
|
||||
cmd_get_son_PID = "systemctl show %s -p %s" % (service, systemctl_PID_name)
|
||||
son_PID = 0
|
||||
# As long as we did not found the PID and that the command is still running
|
||||
while son_PID == 0 and p.poll() == None:
|
||||
while son_PID == 0 and p.poll() is None:
|
||||
# Call systemctl to get the PID
|
||||
# Output of the command is e.g. ControlPID=1234
|
||||
son_PID = subprocess.check_output(cmd_get_son_PID.split()) \
|
||||
|
@ -669,11 +658,12 @@ def _give_lock(action, service, p):
|
|||
|
||||
return son_PID
|
||||
|
||||
|
||||
def _remove_lock(PID_to_remove):
|
||||
# FIXME ironically not concurrency safe because it's not atomic...
|
||||
|
||||
PIDs = filesystem.read_file(MOULINETTE_LOCK).split("\n")
|
||||
PIDs_to_keep = [ PID for PID in PIDs if int(PID) != PID_to_remove ]
|
||||
PIDs_to_keep = [PID for PID in PIDs if int(PID) != PID_to_remove]
|
||||
filesystem.write_to_file(MOULINETTE_LOCK, '\n'.join(PIDs_to_keep))
|
||||
|
||||
|
||||
|
@ -787,6 +777,7 @@ def _find_previous_log_file(file):
|
|||
|
||||
return None
|
||||
|
||||
|
||||
def _get_files_diff(orig_file, new_file, as_string=False, skip_header=True):
|
||||
"""Compare two files and return the differences
|
||||
|
||||
|
@ -927,26 +918,26 @@ def _process_regen_conf(system_conf, new_conf=None, save=True):
|
|||
"""
|
||||
if save:
|
||||
backup_path = os.path.join(BACKUP_CONF_DIR, '{0}-{1}'.format(
|
||||
system_conf.lstrip('/'), time.strftime("%Y%m%d.%H%M%S")))
|
||||
system_conf.lstrip('/'), datetime.utcnow().strftime("%Y%m%d.%H%M%S")))
|
||||
backup_dir = os.path.dirname(backup_path)
|
||||
|
||||
if not os.path.isdir(backup_dir):
|
||||
filesystem.mkdir(backup_dir, 0755, True)
|
||||
filesystem.mkdir(backup_dir, 0o755, True)
|
||||
|
||||
shutil.copy2(system_conf, backup_path)
|
||||
logger.debug(m18n.n('service_conf_file_backed_up',
|
||||
conf=system_conf, backup=backup_path))
|
||||
conf=system_conf, backup=backup_path))
|
||||
|
||||
try:
|
||||
if not new_conf:
|
||||
os.remove(system_conf)
|
||||
logger.debug(m18n.n('service_conf_file_removed',
|
||||
conf=system_conf))
|
||||
conf=system_conf))
|
||||
else:
|
||||
system_dir = os.path.dirname(system_conf)
|
||||
|
||||
if not os.path.isdir(system_dir):
|
||||
filesystem.mkdir(system_dir, 0755, True)
|
||||
filesystem.mkdir(system_dir, 0o755, True)
|
||||
|
||||
shutil.copyfile(new_conf, system_conf)
|
||||
logger.debug(m18n.n('service_conf_file_updated',
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import os
|
||||
import json
|
||||
import errno
|
||||
|
||||
from datetime import datetime
|
||||
from collections import OrderedDict
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
logger = getActionLogger('yunohost.settings')
|
||||
|
@ -29,12 +28,17 @@ SETTINGS_PATH_OTHER_LOCATION = "/etc/yunohost/settings-%s.json"
|
|||
# * string
|
||||
# * enum (in form a python list)
|
||||
|
||||
# we don't store the value in default options
|
||||
DEFAULTS = OrderedDict([
|
||||
("example.bool", {"type": "bool", "default": True}),
|
||||
("example.int", {"type": "int", "default": 42}),
|
||||
("example.string", {"type": "string", "default": "yolo swag"}),
|
||||
("example.enum", {"type": "enum", "default": "a", "choices": ["a", "b", "c"]}),
|
||||
|
||||
# Password Validation
|
||||
# -1 disabled, 0 alert if listed, 1 8-letter, 2 normal, 3 strong, 4 strongest
|
||||
("security.password.admin.strength", {"type": "int", "default": 1}),
|
||||
("security.password.user.strength", {"type": "int", "default": 1}),
|
||||
("service.ssh.allow_deprecated_dsa_hostkey", {"type": "bool", "default": False}),
|
||||
])
|
||||
|
||||
|
||||
|
@ -49,8 +53,7 @@ def settings_get(key, full=False):
|
|||
settings = _get_settings()
|
||||
|
||||
if key not in settings:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'global_settings_key_doesnt_exists', settings_key=key))
|
||||
raise YunohostError('global_settings_key_doesnt_exists', settings_key=key)
|
||||
|
||||
if full:
|
||||
return settings[key]
|
||||
|
@ -78,36 +81,39 @@ def settings_set(key, value):
|
|||
settings = _get_settings()
|
||||
|
||||
if key not in settings:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'global_settings_key_doesnt_exists', settings_key=key))
|
||||
raise YunohostError('global_settings_key_doesnt_exists', settings_key=key)
|
||||
|
||||
key_type = settings[key]["type"]
|
||||
|
||||
if key_type == "bool":
|
||||
if not isinstance(value, bool):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'global_settings_bad_type_for_setting', setting=key,
|
||||
received_type=type(value).__name__, expected_type=key_type))
|
||||
raise YunohostError('global_settings_bad_type_for_setting', setting=key,
|
||||
received_type=type(value).__name__, expected_type=key_type)
|
||||
elif key_type == "int":
|
||||
if not isinstance(value, int) or isinstance(value, bool):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'global_settings_bad_type_for_setting', setting=key,
|
||||
received_type=type(value).__name__, expected_type=key_type))
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
value = int(value)
|
||||
except:
|
||||
raise YunohostError('global_settings_bad_type_for_setting',
|
||||
setting=key,
|
||||
received_type=type(value).__name__,
|
||||
expected_type=key_type)
|
||||
else:
|
||||
raise YunohostError('global_settings_bad_type_for_setting', setting=key,
|
||||
received_type=type(value).__name__, expected_type=key_type)
|
||||
elif key_type == "string":
|
||||
if not isinstance(value, basestring):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'global_settings_bad_type_for_setting', setting=key,
|
||||
received_type=type(value).__name__, expected_type=key_type))
|
||||
raise YunohostError('global_settings_bad_type_for_setting', setting=key,
|
||||
received_type=type(value).__name__, expected_type=key_type)
|
||||
elif key_type == "enum":
|
||||
if value not in settings[key]["choices"]:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'global_settings_bad_choice_for_enum', setting=key,
|
||||
received_type=type(value).__name__,
|
||||
expected_type=", ".join(settings[key]["choices"])))
|
||||
raise YunohostError('global_settings_bad_choice_for_enum', setting=key,
|
||||
received_type=type(value).__name__,
|
||||
expected_type=", ".join(settings[key]["choices"]))
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'global_settings_unknown_type', setting=key,
|
||||
unknown_type=key_type))
|
||||
raise YunohostError('global_settings_unknown_type', setting=key,
|
||||
unknown_type=key_type)
|
||||
|
||||
settings[key]["value"] = value
|
||||
|
||||
|
@ -125,8 +131,7 @@ def settings_reset(key):
|
|||
settings = _get_settings()
|
||||
|
||||
if key not in settings:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||
'global_settings_key_doesnt_exists', settings_key=key))
|
||||
raise YunohostError('global_settings_key_doesnt_exists', settings_key=key)
|
||||
|
||||
settings[key]["value"] = settings[key]["default"]
|
||||
_save_settings(settings)
|
||||
|
@ -147,7 +152,7 @@ def settings_reset_all():
|
|||
# addition but we'll see if this is a common need.
|
||||
# Another solution would be to use etckeeper and integrate those
|
||||
# modification inside of it and take advantage of its git history
|
||||
old_settings_backup_path = SETTINGS_PATH_OTHER_LOCATION % datetime.now().strftime("%F_%X")
|
||||
old_settings_backup_path = SETTINGS_PATH_OTHER_LOCATION % datetime.utcnow().strftime("%F_%X")
|
||||
_save_settings(settings, location=old_settings_backup_path)
|
||||
|
||||
for value in settings.values():
|
||||
|
@ -202,8 +207,7 @@ def _get_settings():
|
|||
setting_key=key))
|
||||
unknown_settings[key] = value
|
||||
except Exception as e:
|
||||
raise MoulinetteError(errno.EIO, m18n.n('global_settings_cant_open_settings', reason=e),
|
||||
exc_info=1)
|
||||
raise YunohostError('global_settings_cant_open_settings', reason=e)
|
||||
|
||||
if unknown_settings:
|
||||
try:
|
||||
|
@ -224,14 +228,10 @@ def _save_settings(settings, location=SETTINGS_PATH):
|
|||
try:
|
||||
result = json.dumps(settings_without_description, indent=4)
|
||||
except Exception as e:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('global_settings_cant_serialize_settings', reason=e),
|
||||
exc_info=1)
|
||||
raise YunohostError('global_settings_cant_serialize_settings', reason=e)
|
||||
|
||||
try:
|
||||
with open(location, "w") as settings_fd:
|
||||
settings_fd.write(result)
|
||||
except Exception as e:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('global_settings_cant_write_settings', reason=e),
|
||||
exc_info=1)
|
||||
raise YunohostError('global_settings_cant_write_settings', reason=e)
|
||||
|
|
|
@ -2,12 +2,10 @@
|
|||
|
||||
import re
|
||||
import os
|
||||
import errno
|
||||
import pwd
|
||||
import subprocess
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.filesystem import read_file, write_to_file, chown, chmod, mkdir
|
||||
|
||||
SSHD_CONFIG_PATH = "/etc/ssh/sshd_config"
|
||||
|
@ -23,7 +21,7 @@ def user_ssh_allow(auth, username):
|
|||
# TODO it would be good to support different kind of shells
|
||||
|
||||
if not _get_user_for_ssh(auth, username):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
|
||||
raise YunohostError('user_unknown', user=username)
|
||||
|
||||
auth.update('uid=%s,ou=users' % username, {'loginShell': '/bin/bash'})
|
||||
|
||||
|
@ -42,7 +40,7 @@ def user_ssh_disallow(auth, username):
|
|||
# TODO it would be good to support different kind of shells
|
||||
|
||||
if not _get_user_for_ssh(auth, username):
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
|
||||
raise YunohostError('user_unknown', user=username)
|
||||
|
||||
auth.update('uid=%s,ou=users' % username, {'loginShell': '/bin/false'})
|
||||
|
||||
|
@ -99,7 +97,7 @@ def user_ssh_add_key(auth, username, key, comment):
|
|||
# create empty file to set good permissions
|
||||
write_to_file(authorized_keys_file, "")
|
||||
chown(authorized_keys_file, uid=user["uid"][0])
|
||||
chmod(authorized_keys_file, 0600)
|
||||
chmod(authorized_keys_file, 0o600)
|
||||
|
||||
authorized_keys_content = read_file(authorized_keys_file)
|
||||
|
||||
|
|
|
@ -7,12 +7,14 @@ sys.path.append("..")
|
|||
def pytest_addoption(parser):
|
||||
parser.addoption("--yunodebug", action="store_true", default=False)
|
||||
|
||||
###############################################################################
|
||||
# Tweak translator to raise exceptions if string keys are not defined #
|
||||
###############################################################################
|
||||
#
|
||||
# Tweak translator to raise exceptions if string keys are not defined #
|
||||
#
|
||||
|
||||
|
||||
old_translate = moulinette.core.Translator.translate
|
||||
|
||||
|
||||
def new_translate(self, key, *args, **kwargs):
|
||||
|
||||
if key not in self._translations[self.default_locale].keys():
|
||||
|
@ -21,14 +23,15 @@ def new_translate(self, key, *args, **kwargs):
|
|||
return old_translate(self, key, *args, **kwargs)
|
||||
moulinette.core.Translator.translate = new_translate
|
||||
|
||||
|
||||
def new_m18nn(self, key, *args, **kwargs):
|
||||
return self._namespaces[self._current_namespace].translate(key, *args, **kwargs)
|
||||
|
||||
moulinette.core.Moulinette18n.n = new_m18nn
|
||||
|
||||
###############################################################################
|
||||
# Init the moulinette to have the cli loggers stuff #
|
||||
###############################################################################
|
||||
#
|
||||
# Init the moulinette to have the cli loggers stuff #
|
||||
#
|
||||
|
||||
|
||||
def pytest_cmdline_main(config):
|
||||
|
|
|
@ -5,7 +5,7 @@ import requests_mock
|
|||
import glob
|
||||
import time
|
||||
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
|
||||
from yunohost.app import app_fetchlist, app_removelist, app_listlists, _using_legacy_appslist_system, _migrate_appslist_system, _register_new_appslist
|
||||
|
||||
|
@ -17,7 +17,7 @@ APPSLISTS_JSON = '/etc/yunohost/appslists.json'
|
|||
def setup_function(function):
|
||||
|
||||
# Clear all appslist
|
||||
files = glob.glob(REPO_PATH+"/*")
|
||||
files = glob.glob(REPO_PATH + "/*")
|
||||
for f in files:
|
||||
os.remove(f)
|
||||
|
||||
|
@ -42,9 +42,9 @@ def cron_job_is_there():
|
|||
return r == 0
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Test listing of appslists and registering of appslists #
|
||||
###############################################################################
|
||||
#
|
||||
# Test listing of appslists and registering of appslists #
|
||||
#
|
||||
|
||||
|
||||
def test_appslist_list_empty():
|
||||
|
@ -79,7 +79,7 @@ def test_appslist_list_register_conflict_name():
|
|||
"""
|
||||
|
||||
_register_new_appslist("https://lol.com/appslist.json", "dummy")
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
_register_new_appslist("https://lol.com/appslist2.json", "dummy")
|
||||
|
||||
appslist_dict = app_listlists()
|
||||
|
@ -94,7 +94,7 @@ def test_appslist_list_register_conflict_url():
|
|||
"""
|
||||
|
||||
_register_new_appslist("https://lol.com/appslist.json", "dummy")
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
_register_new_appslist("https://lol.com/appslist.json", "plopette")
|
||||
|
||||
appslist_dict = app_listlists()
|
||||
|
@ -103,9 +103,9 @@ def test_appslist_list_register_conflict_url():
|
|||
assert "plopette" not in appslist_dict.keys()
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Test fetching of appslists #
|
||||
###############################################################################
|
||||
#
|
||||
# Test fetching of appslists #
|
||||
#
|
||||
|
||||
|
||||
def test_appslist_fetch():
|
||||
|
@ -161,7 +161,7 @@ def test_appslist_fetch_unknownlist():
|
|||
|
||||
assert app_listlists() == {}
|
||||
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
app_fetchlist(name="swag")
|
||||
|
||||
|
||||
|
@ -170,7 +170,7 @@ def test_appslist_fetch_url_but_no_name():
|
|||
Do a fetchlist with url given, but no name given
|
||||
"""
|
||||
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
app_fetchlist(url=URL_OFFICIAL_APP_LIST)
|
||||
|
||||
|
||||
|
@ -244,9 +244,9 @@ def test_appslist_fetch_timeout():
|
|||
app_fetchlist()
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Test remove of appslist #
|
||||
###############################################################################
|
||||
#
|
||||
# Test remove of appslist #
|
||||
#
|
||||
|
||||
|
||||
def test_appslist_remove():
|
||||
|
@ -270,13 +270,13 @@ def test_appslist_remove_unknown():
|
|||
Attempt to remove an unknown list
|
||||
"""
|
||||
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
app_removelist("dummy")
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Test migration from legacy appslist system #
|
||||
###############################################################################
|
||||
#
|
||||
# Test migration from legacy appslist system #
|
||||
#
|
||||
|
||||
|
||||
def add_legacy_cron(name, url):
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import pytest
|
||||
|
||||
from moulinette.core import MoulinetteError, init_authenticator
|
||||
|
||||
from moulinette.core import init_authenticator
|
||||
from yunohost.utils.error import YunohostError
|
||||
from yunohost.app import app_install, app_remove
|
||||
from yunohost.domain import _get_maindomain, domain_url_available, _normalize_domain_path
|
||||
|
||||
|
@ -22,6 +22,7 @@ def setup_function(function):
|
|||
except:
|
||||
pass
|
||||
|
||||
|
||||
def teardown_function(function):
|
||||
|
||||
try:
|
||||
|
@ -43,25 +44,25 @@ def test_urlavailable():
|
|||
assert domain_url_available(auth, maindomain, "/macnuggets")
|
||||
|
||||
# We don't know the domain yolo.swag
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
assert domain_url_available(auth, "yolo.swag", "/macnuggets")
|
||||
|
||||
|
||||
def test_registerurl():
|
||||
|
||||
app_install(auth, "./tests/apps/register_url_app_ynh",
|
||||
args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"))
|
||||
args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"))
|
||||
|
||||
assert not domain_url_available(auth, maindomain, "/urlregisterapp")
|
||||
|
||||
# Try installing at same location
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
app_install(auth, "./tests/apps/register_url_app_ynh",
|
||||
args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"))
|
||||
args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"))
|
||||
|
||||
|
||||
def test_registerurl_baddomain():
|
||||
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
app_install(auth, "./tests/apps/register_url_app_ynh",
|
||||
args="domain=%s&path=%s" % ("yolo.swag", "/urlregisterapp"))
|
||||
args="domain=%s&path=%s" % ("yolo.swag", "/urlregisterapp"))
|
||||
|
|
|
@ -12,18 +12,22 @@ from yunohost.app import app_install, app_remove, app_ssowatconf
|
|||
from yunohost.app import _is_installed
|
||||
from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete
|
||||
from yunohost.domain import _get_maindomain
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
|
||||
# Get main domain
|
||||
maindomain = _get_maindomain()
|
||||
maindomain = ""
|
||||
|
||||
# Instantiate LDAP Authenticator
|
||||
AUTH_IDENTIFIER = ('ldap', 'ldap-anonymous')
|
||||
AUTH_PARAMETERS = {'uri': 'ldap://localhost:389', 'base_dn': 'dc=yunohost,dc=org'}
|
||||
auth = None
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
|
||||
global maindomain
|
||||
maindomain = _get_maindomain()
|
||||
|
||||
print ""
|
||||
|
||||
global auth
|
||||
|
@ -84,9 +88,9 @@ def teardown_function(function):
|
|||
shutil.rmtree("/opt/test_backup_output_directory")
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Helpers #
|
||||
###############################################################################
|
||||
#
|
||||
# Helpers #
|
||||
#
|
||||
|
||||
def app_is_installed(app):
|
||||
|
||||
|
@ -108,6 +112,7 @@ def backup_test_dependencies_are_met():
|
|||
|
||||
return True
|
||||
|
||||
|
||||
def tmp_backup_directory_is_empty():
|
||||
|
||||
if not os.path.exists("/home/yunohost.backup/tmp/"):
|
||||
|
@ -115,6 +120,7 @@ def tmp_backup_directory_is_empty():
|
|||
else:
|
||||
return len(os.listdir('/home/yunohost.backup/tmp/')) == 0
|
||||
|
||||
|
||||
def clean_tmp_backup_directory():
|
||||
|
||||
if tmp_backup_directory_is_empty():
|
||||
|
@ -122,10 +128,10 @@ def clean_tmp_backup_directory():
|
|||
|
||||
mount_lines = subprocess.check_output("mount").split("\n")
|
||||
|
||||
points_to_umount = [ line.split(" ")[2]
|
||||
for line in mount_lines
|
||||
if len(line) >= 3
|
||||
and line.split(" ")[2].startswith("/home/yunohost.backup/tmp") ]
|
||||
points_to_umount = [line.split(" ")[2]
|
||||
for line in mount_lines
|
||||
if len(line) >= 3
|
||||
and line.split(" ")[2].startswith("/home/yunohost.backup/tmp")]
|
||||
|
||||
for point in reversed(points_to_umount):
|
||||
os.system("umount %s" % point)
|
||||
|
@ -135,6 +141,7 @@ def clean_tmp_backup_directory():
|
|||
|
||||
shutil.rmtree("/home/yunohost.backup/tmp/")
|
||||
|
||||
|
||||
def reset_ssowat_conf():
|
||||
|
||||
# Make sure we have a ssowat
|
||||
|
@ -188,14 +195,15 @@ def add_archive_system_from_2p4():
|
|||
os.system("cp ./tests/apps/backup_system_from_2p4/backup.tar.gz \
|
||||
/home/yunohost.backup/archives/backup_system_from_2p4.tar.gz")
|
||||
|
||||
###############################################################################
|
||||
# System backup #
|
||||
###############################################################################
|
||||
#
|
||||
# System backup #
|
||||
#
|
||||
|
||||
|
||||
def test_backup_only_ldap():
|
||||
|
||||
# Create the backup
|
||||
backup_create(ignore_system=False, ignore_apps=True, system=["conf_ldap"])
|
||||
backup_create(system=["conf_ldap"], apps=None)
|
||||
|
||||
archives = backup_list()["archives"]
|
||||
assert len(archives) == 1
|
||||
|
@ -211,20 +219,21 @@ def test_backup_system_part_that_does_not_exists(mocker):
|
|||
mocker.spy(m18n, "n")
|
||||
|
||||
# Create the backup
|
||||
with pytest.raises(MoulinetteError):
|
||||
backup_create(ignore_system=False, ignore_apps=True, system=["yolol"])
|
||||
with pytest.raises(YunohostError):
|
||||
backup_create(system=["yolol"], apps=None)
|
||||
|
||||
m18n.n.assert_any_call('backup_hook_unknown', hook="yolol")
|
||||
m18n.n.assert_any_call('backup_nothings_done')
|
||||
|
||||
###############################################################################
|
||||
# System backup and restore #
|
||||
###############################################################################
|
||||
#
|
||||
# System backup and restore #
|
||||
#
|
||||
|
||||
|
||||
def test_backup_and_restore_all_sys():
|
||||
|
||||
# Create the backup
|
||||
backup_create(ignore_system=False, ignore_apps=True)
|
||||
backup_create(system=[], apps=None)
|
||||
|
||||
archives = backup_list()["archives"]
|
||||
assert len(archives) == 1
|
||||
|
@ -241,40 +250,41 @@ def test_backup_and_restore_all_sys():
|
|||
|
||||
# Restore the backup
|
||||
backup_restore(auth, name=archives[0], force=True,
|
||||
ignore_system=False, ignore_apps=True)
|
||||
system=[], apps=None)
|
||||
|
||||
# Check ssowat conf is back
|
||||
assert os.path.exists("/etc/ssowat/conf.json")
|
||||
|
||||
|
||||
###############################################################################
|
||||
# System restore from 2.4 #
|
||||
###############################################################################
|
||||
#
|
||||
# System restore from 2.4 #
|
||||
#
|
||||
|
||||
@pytest.mark.with_system_archive_from_2p4
|
||||
def test_restore_system_from_Ynh2p4(monkeypatch, mocker):
|
||||
|
||||
# Backup current system
|
||||
backup_create(ignore_system=False, ignore_apps=True)
|
||||
backup_create(system=[], apps=None)
|
||||
archives = backup_list()["archives"]
|
||||
assert len(archives) == 2
|
||||
|
||||
# Restore system archive from 2.4
|
||||
try:
|
||||
backup_restore(auth, name=backup_list()["archives"][1],
|
||||
ignore_system=False,
|
||||
ignore_apps=True,
|
||||
force=True)
|
||||
system=[],
|
||||
apps=None,
|
||||
force=True)
|
||||
finally:
|
||||
# Restore system as it was
|
||||
backup_restore(auth, name=backup_list()["archives"][0],
|
||||
ignore_system=False,
|
||||
ignore_apps=True,
|
||||
force=True)
|
||||
system=[],
|
||||
apps=None,
|
||||
force=True)
|
||||
|
||||
#
|
||||
# App backup #
|
||||
#
|
||||
|
||||
###############################################################################
|
||||
# App backup #
|
||||
###############################################################################
|
||||
|
||||
@pytest.mark.with_backup_recommended_app_installed
|
||||
def test_backup_script_failure_handling(monkeypatch, mocker):
|
||||
|
@ -292,11 +302,12 @@ def test_backup_script_failure_handling(monkeypatch, mocker):
|
|||
monkeypatch.setattr("yunohost.backup.hook_exec", custom_hook_exec)
|
||||
mocker.spy(m18n, "n")
|
||||
|
||||
with pytest.raises(MoulinetteError):
|
||||
backup_create(ignore_system=True, ignore_apps=False, apps=["backup_recommended_app"])
|
||||
with pytest.raises(YunohostError):
|
||||
backup_create(system=None, apps=["backup_recommended_app"])
|
||||
|
||||
m18n.n.assert_any_call('backup_app_failed', app='backup_recommended_app')
|
||||
|
||||
|
||||
@pytest.mark.with_backup_recommended_app_installed
|
||||
def test_backup_not_enough_free_space(monkeypatch, mocker):
|
||||
|
||||
|
@ -312,8 +323,8 @@ def test_backup_not_enough_free_space(monkeypatch, mocker):
|
|||
|
||||
mocker.spy(m18n, "n")
|
||||
|
||||
with pytest.raises(MoulinetteError):
|
||||
backup_create(ignore_system=True, ignore_apps=False, apps=["backup_recommended_app"])
|
||||
with pytest.raises(YunohostError):
|
||||
backup_create(system=None, apps=["backup_recommended_app"])
|
||||
|
||||
m18n.n.assert_any_call('not_enough_disk_space', path=ANY)
|
||||
|
||||
|
@ -324,8 +335,8 @@ def test_backup_app_not_installed(mocker):
|
|||
|
||||
mocker.spy(m18n, "n")
|
||||
|
||||
with pytest.raises(MoulinetteError):
|
||||
backup_create(ignore_system=True, ignore_apps=False, apps=["wordpress"])
|
||||
with pytest.raises(YunohostError):
|
||||
backup_create(system=None, apps=["wordpress"])
|
||||
|
||||
m18n.n.assert_any_call("unbackup_app", app="wordpress")
|
||||
m18n.n.assert_any_call('backup_nothings_done')
|
||||
|
@ -340,8 +351,8 @@ def test_backup_app_with_no_backup_script(mocker):
|
|||
|
||||
mocker.spy(m18n, "n")
|
||||
|
||||
with pytest.raises(MoulinetteError):
|
||||
backup_create(ignore_system=True, ignore_apps=False, apps=["backup_recommended_app"])
|
||||
with pytest.raises(YunohostError):
|
||||
backup_create(system=None, apps=["backup_recommended_app"])
|
||||
|
||||
m18n.n.assert_any_call("backup_with_no_backup_script_for_app", app="backup_recommended_app")
|
||||
m18n.n.assert_any_call('backup_nothings_done')
|
||||
|
@ -359,7 +370,7 @@ def test_backup_app_with_no_restore_script(mocker):
|
|||
# Backuping an app with no restore script will only display a warning to the
|
||||
# user...
|
||||
|
||||
backup_create(ignore_system=True, ignore_apps=False, apps=["backup_recommended_app"])
|
||||
backup_create(system=None, apps=["backup_recommended_app"])
|
||||
|
||||
m18n.n.assert_any_call("backup_with_no_restore_script_for_app", app="backup_recommended_app")
|
||||
|
||||
|
@ -368,7 +379,7 @@ def test_backup_app_with_no_restore_script(mocker):
|
|||
def test_backup_with_different_output_directory():
|
||||
|
||||
# Create the backup
|
||||
backup_create(ignore_system=False, ignore_apps=True, system=["conf_ssh"],
|
||||
backup_create(system=["conf_ssh"], apps=None,
|
||||
output_directory="/opt/test_backup_output_directory",
|
||||
name="backup")
|
||||
|
||||
|
@ -382,10 +393,11 @@ def test_backup_with_different_output_directory():
|
|||
assert len(archives_info["system"].keys()) == 1
|
||||
assert "conf_ssh" in archives_info["system"].keys()
|
||||
|
||||
|
||||
@pytest.mark.clean_opt_dir
|
||||
def test_backup_with_no_compress():
|
||||
# Create the backup
|
||||
backup_create(ignore_system=False, ignore_apps=True, system=["conf_nginx"],
|
||||
backup_create(system=["conf_nginx"], apps=None,
|
||||
output_directory="/opt/test_backup_output_directory",
|
||||
no_compress=True,
|
||||
name="backup")
|
||||
|
@ -393,17 +405,15 @@ def test_backup_with_no_compress():
|
|||
assert os.path.exists("/opt/test_backup_output_directory/info.json")
|
||||
|
||||
|
||||
###############################################################################
|
||||
# App restore #
|
||||
###############################################################################
|
||||
#
|
||||
# App restore #
|
||||
#
|
||||
|
||||
@pytest.mark.with_wordpress_archive_from_2p4
|
||||
def test_restore_app_wordpress_from_Ynh2p4():
|
||||
|
||||
backup_restore(auth, name=backup_list()["archives"][0],
|
||||
ignore_system=True,
|
||||
ignore_apps=False,
|
||||
apps=["wordpress"])
|
||||
backup_restore(auth, system=None, name=backup_list()["archives"][0],
|
||||
apps=["wordpress"])
|
||||
|
||||
|
||||
@pytest.mark.with_wordpress_archive_from_2p4
|
||||
|
@ -419,11 +429,9 @@ def test_restore_app_script_failure_handling(monkeypatch, mocker):
|
|||
|
||||
assert not _is_installed("wordpress")
|
||||
|
||||
with pytest.raises(MoulinetteError):
|
||||
backup_restore(auth, name=backup_list()["archives"][0],
|
||||
ignore_system=True,
|
||||
ignore_apps=False,
|
||||
apps=["wordpress"])
|
||||
with pytest.raises(YunohostError):
|
||||
backup_restore(auth, system=None, name=backup_list()["archives"][0],
|
||||
apps=["wordpress"])
|
||||
|
||||
m18n.n.assert_any_call('restore_app_failed', app='wordpress')
|
||||
m18n.n.assert_any_call('restore_nothings_done')
|
||||
|
@ -442,16 +450,14 @@ def test_restore_app_not_enough_free_space(monkeypatch, mocker):
|
|||
|
||||
assert not _is_installed("wordpress")
|
||||
|
||||
with pytest.raises(MoulinetteError):
|
||||
backup_restore(auth, name=backup_list()["archives"][0],
|
||||
ignore_system=True,
|
||||
ignore_apps=False,
|
||||
apps=["wordpress"])
|
||||
with pytest.raises(YunohostError):
|
||||
backup_restore(auth, system=None, name=backup_list()["archives"][0],
|
||||
apps=["wordpress"])
|
||||
|
||||
m18n.n.assert_any_call('restore_not_enough_disk_space',
|
||||
free_space=0,
|
||||
margin=ANY,
|
||||
needed_space=ANY)
|
||||
free_space=0,
|
||||
margin=ANY,
|
||||
needed_space=ANY)
|
||||
assert not _is_installed("wordpress")
|
||||
|
||||
|
||||
|
@ -463,11 +469,9 @@ def test_restore_app_not_in_backup(mocker):
|
|||
|
||||
mocker.spy(m18n, "n")
|
||||
|
||||
with pytest.raises(MoulinetteError):
|
||||
backup_restore(auth, name=backup_list()["archives"][0],
|
||||
ignore_system=True,
|
||||
ignore_apps=False,
|
||||
apps=["yoloswag"])
|
||||
with pytest.raises(YunohostError):
|
||||
backup_restore(auth, system=None, name=backup_list()["archives"][0],
|
||||
apps=["yoloswag"])
|
||||
|
||||
m18n.n.assert_any_call('backup_archive_app_not_found', app="yoloswag")
|
||||
assert not _is_installed("wordpress")
|
||||
|
@ -479,19 +483,15 @@ def test_restore_app_already_installed(mocker):
|
|||
|
||||
assert not _is_installed("wordpress")
|
||||
|
||||
backup_restore(auth, name=backup_list()["archives"][0],
|
||||
ignore_system=True,
|
||||
ignore_apps=False,
|
||||
apps=["wordpress"])
|
||||
backup_restore(auth, system=None, name=backup_list()["archives"][0],
|
||||
apps=["wordpress"])
|
||||
|
||||
assert _is_installed("wordpress")
|
||||
|
||||
mocker.spy(m18n, "n")
|
||||
with pytest.raises(MoulinetteError):
|
||||
backup_restore(auth, name=backup_list()["archives"][0],
|
||||
ignore_system=True,
|
||||
ignore_apps=False,
|
||||
apps=["wordpress"])
|
||||
with pytest.raises(YunohostError):
|
||||
backup_restore(auth, system=None, name=backup_list()["archives"][0],
|
||||
apps=["wordpress"])
|
||||
|
||||
m18n.n.assert_any_call('restore_already_installed_app', app="wordpress")
|
||||
m18n.n.assert_any_call('restore_nothings_done')
|
||||
|
@ -520,7 +520,7 @@ def test_backup_and_restore_with_ynh_restore():
|
|||
def _test_backup_and_restore_app(app):
|
||||
|
||||
# Create a backup of this app
|
||||
backup_create(ignore_system=True, ignore_apps=False, apps=[app])
|
||||
backup_create(system=None, apps=[app])
|
||||
|
||||
archives = backup_list()["archives"]
|
||||
assert len(archives) == 1
|
||||
|
@ -535,14 +535,15 @@ def _test_backup_and_restore_app(app):
|
|||
assert not app_is_installed(app)
|
||||
|
||||
# Restore the app
|
||||
backup_restore(auth, name=archives[0], ignore_system=True,
|
||||
ignore_apps=False, apps=[app])
|
||||
backup_restore(auth, system=None, name=archives[0],
|
||||
apps=[app])
|
||||
|
||||
assert app_is_installed(app)
|
||||
|
||||
###############################################################################
|
||||
# Some edge cases #
|
||||
###############################################################################
|
||||
#
|
||||
# Some edge cases #
|
||||
#
|
||||
|
||||
|
||||
def test_restore_archive_with_no_json(mocker):
|
||||
|
||||
|
@ -553,9 +554,8 @@ def test_restore_archive_with_no_json(mocker):
|
|||
assert "badbackup" in backup_list()["archives"]
|
||||
|
||||
mocker.spy(m18n, "n")
|
||||
with pytest.raises(MoulinetteError):
|
||||
backup_restore(auth, name="badbackup", force=True,
|
||||
ignore_system=False, ignore_apps=False)
|
||||
with pytest.raises(YunohostError):
|
||||
backup_restore(auth, name="badbackup", force=True)
|
||||
m18n.n.assert_any_call('backup_invalid_archive')
|
||||
|
||||
|
||||
|
@ -567,7 +567,7 @@ def test_backup_binds_are_readonly(monkeypatch):
|
|||
|
||||
confssh = os.path.join(self.work_dir, "conf/ssh")
|
||||
output = subprocess.check_output("touch %s/test 2>&1 || true" % confssh,
|
||||
shell=True)
|
||||
shell=True, env={'LANG': 'en_US.UTF-8'})
|
||||
|
||||
assert "Read-only file system" in output
|
||||
|
||||
|
@ -577,7 +577,7 @@ def test_backup_binds_are_readonly(monkeypatch):
|
|||
self.clean()
|
||||
|
||||
monkeypatch.setattr("yunohost.backup.BackupMethod.mount_and_backup",
|
||||
custom_mount_and_backup)
|
||||
custom_mount_and_backup)
|
||||
|
||||
# Create the backup
|
||||
backup_create(ignore_system=False, ignore_apps=True)
|
||||
backup_create(system=[])
|
||||
|
|
|
@ -6,7 +6,7 @@ from moulinette.core import init_authenticator
|
|||
from yunohost.app import app_install, app_change_url, app_remove, app_map
|
||||
from yunohost.domain import _get_maindomain
|
||||
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
|
||||
# Instantiate LDAP Authenticator
|
||||
AUTH_IDENTIFIER = ('ldap', 'ldap-anonymous')
|
||||
|
@ -38,7 +38,7 @@ def check_changeurl_app(path):
|
|||
|
||||
assert appmap[maindomain][path + "/"]["id"] == "change_url_app"
|
||||
|
||||
r = requests.get("https://%s%s/" % (maindomain, path), verify=False)
|
||||
r = requests.get("https://127.0.0.1%s/" % path, headers={"domain": maindomain}, verify=False)
|
||||
assert r.status_code == 200
|
||||
|
||||
|
||||
|
@ -53,9 +53,10 @@ def test_appchangeurl():
|
|||
|
||||
check_changeurl_app("/newchangeurl")
|
||||
|
||||
|
||||
def test_appchangeurl_sameurl():
|
||||
install_changeurl_app("/changeurl")
|
||||
check_changeurl_app("/changeurl")
|
||||
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
app_change_url(auth, "change_url_app", maindomain, "changeurl")
|
||||
|
|
|
@ -2,7 +2,7 @@ import os
|
|||
import json
|
||||
import pytest
|
||||
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
|
||||
from yunohost.settings import settings_get, settings_list, _get_settings, \
|
||||
settings_set, settings_reset, settings_reset_all, \
|
||||
|
@ -18,7 +18,8 @@ def teardown_function(function):
|
|||
|
||||
|
||||
def test_settings_get_bool():
|
||||
assert settings_get("example.bool") == True
|
||||
assert settings_get("example.bool")
|
||||
|
||||
|
||||
def test_settings_get_full_bool():
|
||||
assert settings_get("example.bool", True) == {"type": "bool", "value": True, "default": True, "description": "Example boolean option"}
|
||||
|
@ -27,6 +28,7 @@ def test_settings_get_full_bool():
|
|||
def test_settings_get_int():
|
||||
assert settings_get("example.int") == 42
|
||||
|
||||
|
||||
def test_settings_get_full_int():
|
||||
assert settings_get("example.int", True) == {"type": "int", "value": 42, "default": 42, "description": "Example int option"}
|
||||
|
||||
|
@ -34,6 +36,7 @@ def test_settings_get_full_int():
|
|||
def test_settings_get_string():
|
||||
assert settings_get("example.string") == "yolo swag"
|
||||
|
||||
|
||||
def test_settings_get_full_string():
|
||||
assert settings_get("example.string", True) == {"type": "string", "value": "yolo swag", "default": "yolo swag", "description": "Example string option"}
|
||||
|
||||
|
@ -41,12 +44,13 @@ def test_settings_get_full_string():
|
|||
def test_settings_get_enum():
|
||||
assert settings_get("example.enum") == "a"
|
||||
|
||||
|
||||
def test_settings_get_full_enum():
|
||||
assert settings_get("example.enum", True) == {"type": "enum", "value": "a", "default": "a", "description": "Example enum option", "choices": ["a", "b", "c"]}
|
||||
|
||||
|
||||
def test_settings_get_doesnt_exists():
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
settings_get("doesnt.exists")
|
||||
|
||||
|
||||
|
@ -70,39 +74,39 @@ def test_settings_set_enum():
|
|||
|
||||
|
||||
def test_settings_set_doesexit():
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
settings_set("doesnt.exist", True)
|
||||
|
||||
|
||||
def test_settings_set_bad_type_bool():
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
settings_set("example.bool", 42)
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
settings_set("example.bool", "pouet")
|
||||
|
||||
|
||||
def test_settings_set_bad_type_int():
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
settings_set("example.int", True)
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
settings_set("example.int", "pouet")
|
||||
|
||||
|
||||
def test_settings_set_bad_type_string():
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
settings_set("example.string", True)
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
settings_set("example.string", 42)
|
||||
|
||||
|
||||
def test_settings_set_bad_value_enum():
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
settings_set("example.enum", True)
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
settings_set("example.enum", "e")
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
settings_set("example.enum", 42)
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
settings_set("example.enum", "pouet")
|
||||
|
||||
|
||||
|
@ -119,7 +123,7 @@ def test_reset():
|
|||
|
||||
|
||||
def test_settings_reset_doesexit():
|
||||
with pytest.raises(MoulinetteError):
|
||||
with pytest.raises(YunohostError):
|
||||
settings_reset("doesnt.exist")
|
||||
|
||||
|
||||
|
@ -152,7 +156,6 @@ def test_reset_all_backup():
|
|||
assert settings_after_modification == json.load(open(old_settings_backup_path, "r"))
|
||||
|
||||
|
||||
|
||||
def test_unknown_keys():
|
||||
unknown_settings_path = SETTINGS_PATH_OTHER_LOCATION % "unknown"
|
||||
unknown_setting = {
|
||||
|
|
|
@ -27,8 +27,6 @@ import re
|
|||
import os
|
||||
import yaml
|
||||
import json
|
||||
import errno
|
||||
import logging
|
||||
import subprocess
|
||||
import pwd
|
||||
import socket
|
||||
|
@ -40,7 +38,8 @@ import apt
|
|||
import apt.progress
|
||||
|
||||
from moulinette import msettings, msignals, m18n
|
||||
from moulinette.core import MoulinetteError, init_authenticator
|
||||
from moulinette.core import init_authenticator
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.process import check_output
|
||||
from moulinette.utils.filesystem import read_json, write_to_json
|
||||
|
@ -112,13 +111,13 @@ def tools_ldapinit():
|
|||
pwd.getpwnam("admin")
|
||||
except KeyError:
|
||||
logger.error(m18n.n('ldap_init_failed_to_create_admin'))
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('installation_failed'))
|
||||
raise YunohostError('installation_failed')
|
||||
|
||||
logger.success(m18n.n('ldap_initialized'))
|
||||
return auth
|
||||
|
||||
|
||||
def tools_adminpw(auth, new_password):
|
||||
def tools_adminpw(auth, new_password, check_strength=True):
|
||||
"""
|
||||
Change admin password
|
||||
|
||||
|
@ -127,15 +126,35 @@ def tools_adminpw(auth, new_password):
|
|||
|
||||
"""
|
||||
from yunohost.user import _hash_user_password
|
||||
from yunohost.utils.password import assert_password_is_strong_enough
|
||||
import spwd
|
||||
|
||||
if check_strength:
|
||||
assert_password_is_strong_enough("admin", new_password)
|
||||
|
||||
new_hash = _hash_user_password(new_password)
|
||||
|
||||
try:
|
||||
auth.update("cn=admin", {
|
||||
"userPassword": _hash_user_password(new_password),
|
||||
})
|
||||
auth.update("cn=admin", {"userPassword": new_hash, })
|
||||
except:
|
||||
logger.exception('unable to change admin password')
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('admin_password_change_failed'))
|
||||
raise YunohostError('admin_password_change_failed')
|
||||
else:
|
||||
# Write as root password
|
||||
try:
|
||||
hash_root = spwd.getspnam("root").sp_pwd
|
||||
|
||||
with open('/etc/shadow', 'r') as before_file:
|
||||
before = before_file.read()
|
||||
|
||||
with open('/etc/shadow', 'w') as after_file:
|
||||
after_file.write(before.replace("root:" + hash_root,
|
||||
"root:" + new_hash.replace('{CRYPT}', '')))
|
||||
except IOError:
|
||||
logger.warning(m18n.n('root_password_desynchronized'))
|
||||
return
|
||||
|
||||
logger.info(m18n.n("root_password_replaced_by_admin_password"))
|
||||
logger.success(m18n.n('admin_password_changed'))
|
||||
|
||||
|
||||
|
@ -155,7 +174,7 @@ def tools_maindomain(operation_logger, auth, new_domain=None):
|
|||
|
||||
# Check domain exists
|
||||
if new_domain not in domain_list(auth)['domains']:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
|
||||
raise YunohostError('domain_unknown')
|
||||
|
||||
operation_logger.related_to.append(('domain', new_domain))
|
||||
operation_logger.start()
|
||||
|
@ -178,7 +197,7 @@ def tools_maindomain(operation_logger, auth, new_domain=None):
|
|||
_set_maindomain(new_domain)
|
||||
except Exception as e:
|
||||
logger.warning("%s" % e, exc_info=1)
|
||||
raise MoulinetteError(errno.EPERM, m18n.n('maindomain_change_failed'))
|
||||
raise YunohostError('maindomain_change_failed')
|
||||
|
||||
_set_hostname(new_domain)
|
||||
|
||||
|
@ -187,7 +206,7 @@ def tools_maindomain(operation_logger, auth, new_domain=None):
|
|||
|
||||
# Regen configurations
|
||||
try:
|
||||
with open('/etc/yunohost/installed', 'r') as f:
|
||||
with open('/etc/yunohost/installed', 'r'):
|
||||
service_regen_conf()
|
||||
except IOError:
|
||||
pass
|
||||
|
@ -227,7 +246,7 @@ def _set_hostname(hostname, pretty_hostname=None):
|
|||
if p.returncode != 0:
|
||||
logger.warning(command)
|
||||
logger.warning(out)
|
||||
raise MoulinetteError(errno.EIO, m18n.n('domain_hostname_failed'))
|
||||
raise YunohostError('domain_hostname_failed')
|
||||
else:
|
||||
logger.debug(out)
|
||||
|
||||
|
@ -245,12 +264,13 @@ def _is_inside_container():
|
|||
stderr=subprocess.STDOUT)
|
||||
|
||||
out, _ = p.communicate()
|
||||
container = ['lxc','lxd','docker']
|
||||
container = ['lxc', 'lxd', 'docker']
|
||||
return out.split()[0] in container
|
||||
|
||||
|
||||
@is_unit_operation()
|
||||
def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
|
||||
def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
|
||||
force_password=False):
|
||||
"""
|
||||
YunoHost post-install
|
||||
|
||||
|
@ -261,12 +281,17 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
|
|||
password -- YunoHost admin password
|
||||
|
||||
"""
|
||||
from yunohost.utils.password import assert_password_is_strong_enough
|
||||
|
||||
dyndns_provider = "dyndns.yunohost.org"
|
||||
|
||||
# Do some checks at first
|
||||
if os.path.isfile('/etc/yunohost/installed'):
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('yunohost_already_installed'))
|
||||
raise YunohostError('yunohost_already_installed')
|
||||
|
||||
# Check password
|
||||
if not force_password:
|
||||
assert_password_is_strong_enough("admin", password)
|
||||
|
||||
if not ignore_dyndns:
|
||||
# Check if yunohost dyndns can handle the given domain
|
||||
|
@ -291,9 +316,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
|
|||
dyndns = True
|
||||
# If not, abort the postinstall
|
||||
else:
|
||||
raise MoulinetteError(errno.EEXIST,
|
||||
m18n.n('dyndns_unavailable',
|
||||
domain=domain))
|
||||
raise YunohostError('dyndns_unavailable', domain=domain)
|
||||
else:
|
||||
dyndns = False
|
||||
else:
|
||||
|
@ -335,8 +358,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
|
|||
with open('/etc/ssowat/conf.json.persistent') as json_conf:
|
||||
ssowat_conf = json.loads(str(json_conf.read()))
|
||||
except ValueError as e:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('ssowat_persistent_conf_read_error', error=str(e)))
|
||||
raise YunohostError('ssowat_persistent_conf_read_error', error=str(e))
|
||||
except IOError:
|
||||
ssowat_conf = {}
|
||||
|
||||
|
@ -349,16 +371,16 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
|
|||
with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
|
||||
json.dump(ssowat_conf, f, sort_keys=True, indent=4)
|
||||
except IOError as e:
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('ssowat_persistent_conf_write_error', error=str(e)))
|
||||
raise YunohostError('ssowat_persistent_conf_write_error', error=str(e))
|
||||
|
||||
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
|
||||
|
||||
# Create SSL CA
|
||||
service_regen_conf(['ssl'], force=True)
|
||||
ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA'
|
||||
# (Update the serial so that it's specific to this very instance)
|
||||
os.system("openssl rand -hex 19 > %s/serial" % ssl_dir)
|
||||
commands = [
|
||||
'echo "01" > %s/serial' % ssl_dir,
|
||||
'rm %s/index.txt' % ssl_dir,
|
||||
'touch %s/index.txt' % ssl_dir,
|
||||
'cp %s/openssl.cnf %s/openssl.ca.cnf' % (ssl_dir, ssl_dir),
|
||||
|
@ -376,8 +398,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
|
|||
|
||||
if p.returncode != 0:
|
||||
logger.warning(out)
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('yunohost_ca_creation_failed'))
|
||||
raise YunohostError('yunohost_ca_creation_failed')
|
||||
else:
|
||||
logger.debug(out)
|
||||
|
||||
|
@ -389,7 +410,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
|
|||
tools_maindomain(auth, domain)
|
||||
|
||||
# Change LDAP admin password
|
||||
tools_adminpw(auth, password)
|
||||
tools_adminpw(auth, password, check_strength=not force_password)
|
||||
|
||||
# Enable UPnP silently and reload firewall
|
||||
firewall_upnp('enable', no_refresh=True)
|
||||
|
@ -404,7 +425,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
|
|||
_install_appslist_fetch_cron()
|
||||
|
||||
# Init migrations (skip them, no need to run them on a fresh system)
|
||||
tools_migrations_migrate(skip=True, auto=True)
|
||||
_skip_all_migrations()
|
||||
|
||||
os.system('touch /etc/yunohost/installed')
|
||||
|
||||
|
@ -413,6 +434,24 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
|
|||
service_start("yunohost-firewall")
|
||||
|
||||
service_regen_conf(force=True)
|
||||
|
||||
# Restore original ssh conf, as chosen by the
|
||||
# admin during the initial install
|
||||
#
|
||||
# c.f. the install script and in particular
|
||||
# https://github.com/YunoHost/install_script/pull/50
|
||||
# The user can now choose during the install to keep
|
||||
# the initial, existing sshd configuration
|
||||
# instead of YunoHost's recommended conf
|
||||
#
|
||||
original_sshd_conf = '/etc/ssh/sshd_config.before_yunohost'
|
||||
if os.path.exists(original_sshd_conf):
|
||||
os.rename(original_sshd_conf, '/etc/ssh/sshd_config')
|
||||
else:
|
||||
# We need to explicitly ask the regen conf to regen ssh
|
||||
# (by default, i.e. first argument = None, it won't because it's too touchy)
|
||||
service_regen_conf(names=["ssh"], force=True)
|
||||
|
||||
logger.success(m18n.n('yunohost_configured'))
|
||||
|
||||
logger.warning(m18n.n('recommend_to_add_first_user'))
|
||||
|
@ -435,7 +474,7 @@ def tools_update(ignore_apps=False, ignore_packages=False):
|
|||
# Update APT cache
|
||||
logger.debug(m18n.n('updating_apt_cache'))
|
||||
if not cache.update():
|
||||
raise MoulinetteError(errno.EPERM, m18n.n('update_cache_failed'))
|
||||
raise YunohostError('update_cache_failed')
|
||||
|
||||
cache.open(None)
|
||||
cache.upgrade(True)
|
||||
|
@ -454,7 +493,7 @@ def tools_update(ignore_apps=False, ignore_packages=False):
|
|||
if not ignore_apps:
|
||||
try:
|
||||
app_fetchlist()
|
||||
except MoulinetteError:
|
||||
except YunohostError:
|
||||
# FIXME : silent exception !?
|
||||
pass
|
||||
|
||||
|
@ -498,7 +537,7 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal
|
|||
# If API call
|
||||
if is_api:
|
||||
critical_packages = ("moulinette", "yunohost",
|
||||
"yunohost-admin", "ssowat", "python")
|
||||
"yunohost-admin", "ssowat", "python")
|
||||
critical_upgrades = set()
|
||||
|
||||
for pkg in cache.get_changes():
|
||||
|
@ -534,7 +573,6 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal
|
|||
else:
|
||||
logger.info(m18n.n('packages_no_upgrade'))
|
||||
|
||||
|
||||
if not ignore_apps:
|
||||
try:
|
||||
app_upgrade(auth)
|
||||
|
@ -585,7 +623,7 @@ def tools_diagnosis(auth, private=False):
|
|||
diagnosis['system'] = OrderedDict()
|
||||
try:
|
||||
disks = monitor_disk(units=['filesystem'], human_readable=True)
|
||||
except (MoulinetteError, Fault) as e:
|
||||
except (YunohostError, Fault) as e:
|
||||
logger.warning(m18n.n('diagnosis_monitor_disk_error', error=format(e)), exc_info=1)
|
||||
else:
|
||||
diagnosis['system']['disks'] = {}
|
||||
|
@ -601,7 +639,7 @@ def tools_diagnosis(auth, private=False):
|
|||
|
||||
try:
|
||||
system = monitor_system(units=['cpu', 'memory'], human_readable=True)
|
||||
except MoulinetteError as e:
|
||||
except YunohostError as e:
|
||||
logger.warning(m18n.n('diagnosis_monitor_system_error', error=format(e)), exc_info=1)
|
||||
else:
|
||||
diagnosis['system']['memory'] = {
|
||||
|
@ -627,7 +665,7 @@ def tools_diagnosis(auth, private=False):
|
|||
# YNH Applications
|
||||
try:
|
||||
applications = app_list()['apps']
|
||||
except MoulinetteError as e:
|
||||
except YunohostError as e:
|
||||
diagnosis['applications'] = m18n.n('diagnosis_no_apps')
|
||||
else:
|
||||
diagnosis['applications'] = {}
|
||||
|
@ -678,7 +716,7 @@ def _check_if_vulnerable_to_meltdown():
|
|||
try:
|
||||
call = subprocess.Popen("bash %s --batch json --variant 3" %
|
||||
SCRIPT_PATH, shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
|
||||
output, _ = call.communicate()
|
||||
|
@ -759,7 +797,7 @@ def tools_migrations_list(pending=False, done=False):
|
|||
|
||||
# Check for option conflict
|
||||
if pending and done:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n("migrations_list_conflict_pending_done"))
|
||||
raise YunohostError("migrations_list_conflict_pending_done")
|
||||
|
||||
# Get all migrations
|
||||
migrations = _get_migrations_list()
|
||||
|
@ -774,12 +812,12 @@ def tools_migrations_list(pending=False, done=False):
|
|||
migrations = [m for m in migrations if m.number > last_migration]
|
||||
|
||||
# Reduce to dictionnaries
|
||||
migrations = [{ "id": migration.id,
|
||||
"number": migration.number,
|
||||
"name": migration.name,
|
||||
"mode": migration.mode,
|
||||
"description": migration.description,
|
||||
"disclaimer": migration.disclaimer } for migration in migrations ]
|
||||
migrations = [{"id": migration.id,
|
||||
"number": migration.number,
|
||||
"name": migration.name,
|
||||
"mode": migration.mode,
|
||||
"description": migration.description,
|
||||
"disclaimer": migration.disclaimer} for migration in migrations]
|
||||
|
||||
return {"migrations": migrations}
|
||||
|
||||
|
@ -816,7 +854,7 @@ def tools_migrations_migrate(target=None, skip=False, auto=False, accept_disclai
|
|||
|
||||
# validate input, target must be "0" or a valid number
|
||||
elif target != 0 and target not in all_migration_numbers:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('migrations_bad_value_for_target', ", ".join(map(str, all_migration_numbers))))
|
||||
raise YunohostError('migrations_bad_value_for_target', ", ".join(map(str, all_migration_numbers)))
|
||||
|
||||
logger.debug(m18n.n('migrations_current_target', target))
|
||||
|
||||
|
@ -844,38 +882,41 @@ def tools_migrations_migrate(target=None, skip=False, auto=False, accept_disclai
|
|||
else: # can't happen, this case is handle before
|
||||
raise Exception()
|
||||
|
||||
# If we are migrating in "automatic mode" (i.e. from debian
|
||||
# configure during an upgrade of the package) but we are asked to run
|
||||
# migrations is to be ran manually by the user
|
||||
manual_migrations = [m for m in migrations if m.mode == "manual"]
|
||||
if not skip and auto and manual_migrations:
|
||||
for m in manual_migrations:
|
||||
logger.warn(m18n.n('migrations_to_be_ran_manually',
|
||||
number=m.number,
|
||||
name=m.name))
|
||||
return
|
||||
|
||||
# If some migrations have disclaimers, require the --accept-disclaimer
|
||||
# option
|
||||
migrations_with_disclaimer = [m for m in migrations if m.disclaimer]
|
||||
if not skip and not accept_disclaimer and migrations_with_disclaimer:
|
||||
for m in migrations_with_disclaimer:
|
||||
logger.warn(m18n.n('migrations_need_to_accept_disclaimer',
|
||||
number=m.number,
|
||||
name=m.name,
|
||||
disclaimer=m.disclaimer))
|
||||
return
|
||||
|
||||
# effectively run selected migrations
|
||||
for migration in migrations:
|
||||
|
||||
if not skip:
|
||||
# If we are migrating in "automatic mode" (i.e. from debian configure
|
||||
# during an upgrade of the package) but we are asked to run migrations
|
||||
# to be ran manually by the user, stop there and ask the user to
|
||||
# run the migration manually.
|
||||
if auto and migration.mode == "manual":
|
||||
logger.warn(m18n.n('migrations_to_be_ran_manually',
|
||||
number=migration.number,
|
||||
name=migration.name))
|
||||
break
|
||||
|
||||
# If some migrations have disclaimers,
|
||||
if migration.disclaimer:
|
||||
# require the --accept-disclaimer option. Otherwise, stop everything
|
||||
# here and display the disclaimer
|
||||
if not accept_disclaimer:
|
||||
logger.warn(m18n.n('migrations_need_to_accept_disclaimer',
|
||||
number=migration.number,
|
||||
name=migration.name,
|
||||
disclaimer=migration.disclaimer))
|
||||
break
|
||||
# --accept-disclaimer will only work for the first migration
|
||||
else:
|
||||
accept_disclaimer = False
|
||||
|
||||
# Start register change on system
|
||||
operation_logger= OperationLogger('tools_migrations_migrate_' + mode)
|
||||
operation_logger = OperationLogger('tools_migrations_migrate_' + mode)
|
||||
operation_logger.start()
|
||||
|
||||
if not skip:
|
||||
|
||||
logger.warn(m18n.n('migrations_show_currently_running_migration',
|
||||
logger.info(m18n.n('migrations_show_currently_running_migration',
|
||||
number=migration.number, name=migration.name))
|
||||
|
||||
try:
|
||||
|
@ -890,12 +931,15 @@ def tools_migrations_migrate(target=None, skip=False, auto=False, accept_disclai
|
|||
# migration failed, let's stop here but still update state because
|
||||
# we managed to run the previous ones
|
||||
msg = m18n.n('migrations_migration_has_failed',
|
||||
exception=e,
|
||||
number=migration.number,
|
||||
name=migration.name)
|
||||
exception=e,
|
||||
number=migration.number,
|
||||
name=migration.name)
|
||||
logger.error(msg, exc_info=1)
|
||||
operation_logger.error(msg)
|
||||
break
|
||||
else:
|
||||
logger.success(m18n.n('migrations_success',
|
||||
number=migration.number, name=migration.name))
|
||||
|
||||
else: # if skip
|
||||
logger.warn(m18n.n('migrations_skip_migration',
|
||||
|
@ -910,6 +954,10 @@ def tools_migrations_migrate(target=None, skip=False, auto=False, accept_disclai
|
|||
|
||||
operation_logger.success()
|
||||
|
||||
# Skip migrations one at a time
|
||||
if skip:
|
||||
break
|
||||
|
||||
# special case where we want to go back from the start
|
||||
if target == 0:
|
||||
state["last_run_migration"] = None
|
||||
|
@ -959,7 +1007,7 @@ def _get_migrations_list():
|
|||
migrations = []
|
||||
|
||||
try:
|
||||
import data_migrations
|
||||
from . import data_migrations
|
||||
except ImportError:
|
||||
# not data migrations present, return empty list
|
||||
return migrations
|
||||
|
@ -982,7 +1030,7 @@ def _get_migration_by_name(migration_name):
|
|||
"""
|
||||
|
||||
try:
|
||||
import data_migrations
|
||||
from . import data_migrations
|
||||
except ImportError:
|
||||
raise AssertionError("Unable to find migration with name %s" % migration_name)
|
||||
|
||||
|
@ -1001,7 +1049,7 @@ def _load_migration(migration_file):
|
|||
number, name = migration_id.split("_", 1)
|
||||
|
||||
logger.debug(m18n.n('migrations_loading_migration',
|
||||
number=number, name=name))
|
||||
number=number, name=name))
|
||||
|
||||
try:
|
||||
# this is python builtin method to import a module using a name, we
|
||||
|
@ -1013,8 +1061,28 @@ def _load_migration(migration_file):
|
|||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('migrations_error_failed_to_load_migration',
|
||||
number=number, name=name))
|
||||
raise YunohostError('migrations_error_failed_to_load_migration',
|
||||
number=number, name=name)
|
||||
|
||||
|
||||
def _skip_all_migrations():
|
||||
"""
|
||||
Skip all pending migrations.
|
||||
This is meant to be used during postinstall to
|
||||
initialize the migration system.
|
||||
"""
|
||||
state = tools_migrations_state()
|
||||
|
||||
# load all migrations
|
||||
migrations = _get_migrations_list()
|
||||
migrations = sorted(migrations, key=lambda x: x.number)
|
||||
last_migration = migrations[-1]
|
||||
|
||||
state["last_run_migration"] = {
|
||||
"number": last_migration.number,
|
||||
"name": last_migration.name
|
||||
}
|
||||
write_to_json(MIGRATIONS_STATE_PATH, state)
|
||||
|
||||
|
||||
class Migration(object):
|
||||
|
|
|
@ -27,20 +27,20 @@ import os
|
|||
import re
|
||||
import pwd
|
||||
import json
|
||||
import errno
|
||||
import crypt
|
||||
import random
|
||||
import string
|
||||
import subprocess
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from yunohost.service import service_status
|
||||
from yunohost.log import is_unit_operation
|
||||
|
||||
logger = getActionLogger('yunohost.user')
|
||||
|
||||
|
||||
def user_list(auth, fields=None):
|
||||
"""
|
||||
List users
|
||||
|
@ -71,8 +71,7 @@ def user_list(auth, fields=None):
|
|||
if attr in keys:
|
||||
attrs.append(attr)
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('field_invalid', attr))
|
||||
raise YunohostError('field_invalid', attr)
|
||||
else:
|
||||
attrs = ['uid', 'cn', 'mail', 'mailuserquota', 'loginShell']
|
||||
|
||||
|
@ -100,7 +99,7 @@ def user_list(auth, fields=None):
|
|||
|
||||
@is_unit_operation([('username', 'user')])
|
||||
def user_create(operation_logger, auth, username, firstname, lastname, mail, password,
|
||||
mailbox_quota="0"):
|
||||
mailbox_quota="0"):
|
||||
"""
|
||||
Create user
|
||||
|
||||
|
@ -116,6 +115,10 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
|
|||
from yunohost.domain import domain_list, _get_maindomain
|
||||
from yunohost.hook import hook_callback
|
||||
from yunohost.app import app_ssowatconf
|
||||
from yunohost.utils.password import assert_password_is_strong_enough
|
||||
|
||||
# Ensure sufficiently complex password
|
||||
assert_password_is_strong_enough("user", password)
|
||||
|
||||
# Validate uniqueness of username and mail in LDAP
|
||||
auth.validate_uniqueness({
|
||||
|
@ -126,13 +129,22 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
|
|||
# Validate uniqueness of username in system users
|
||||
all_existing_usernames = {x.pw_name for x in pwd.getpwall()}
|
||||
if username in all_existing_usernames:
|
||||
raise MoulinetteError(errno.EEXIST, m18n.n('system_username_exists'))
|
||||
raise YunohostError('system_username_exists')
|
||||
|
||||
main_domain = _get_maindomain()
|
||||
aliases = [
|
||||
'root@' + main_domain,
|
||||
'admin@' + main_domain,
|
||||
'webmaster@' + main_domain,
|
||||
'postmaster@' + main_domain,
|
||||
]
|
||||
|
||||
if mail in aliases:
|
||||
raise YunohostError('mail_unavailable')
|
||||
|
||||
# Check that the mail domain exists
|
||||
if mail.split("@")[1] not in domain_list(auth)['domains']:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('mail_domain_unknown',
|
||||
domain=mail.split("@")[1]))
|
||||
raise YunohostError('mail_domain_unknown', domain=mail.split("@")[1])
|
||||
|
||||
operation_logger.start()
|
||||
|
||||
|
@ -166,13 +178,6 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
|
|||
|
||||
# If it is the first user, add some aliases
|
||||
if not auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=*'):
|
||||
main_domain = _get_maindomain()
|
||||
aliases = [
|
||||
'root@' + main_domain,
|
||||
'admin@' + main_domain,
|
||||
'webmaster@' + main_domain,
|
||||
'postmaster@' + main_domain,
|
||||
]
|
||||
attr_dict['mail'] = [attr_dict['mail']] + aliases
|
||||
|
||||
# If exists, remove the redirection from the SSO
|
||||
|
@ -180,8 +185,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
|
|||
with open('/etc/ssowat/conf.json.persistent') as json_conf:
|
||||
ssowat_conf = json.loads(str(json_conf.read()))
|
||||
except ValueError as e:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('ssowat_persistent_conf_read_error', error=e.strerror))
|
||||
raise YunohostError('ssowat_persistent_conf_read_error', error=e.strerror)
|
||||
except IOError:
|
||||
ssowat_conf = {}
|
||||
|
||||
|
@ -191,8 +195,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
|
|||
with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
|
||||
json.dump(ssowat_conf, f, sort_keys=True, indent=4)
|
||||
except IOError as e:
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('ssowat_persistent_conf_write_error', error=e.strerror))
|
||||
raise YunohostError('ssowat_persistent_conf_write_error', error=e.strerror)
|
||||
|
||||
if auth.add('uid=%s,ou=users' % username, attr_dict):
|
||||
# Invalidate passwd to take user creation into account
|
||||
|
@ -218,7 +221,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
|
|||
|
||||
return {'fullname': fullname, 'username': username, 'mail': mail}
|
||||
|
||||
raise MoulinetteError(169, m18n.n('user_creation_failed'))
|
||||
raise YunohostError('user_creation_failed')
|
||||
|
||||
|
||||
@is_unit_operation([('username', 'user')])
|
||||
|
@ -248,8 +251,9 @@ def user_delete(operation_logger, auth, username, purge=False):
|
|||
if auth.update('cn=sftpusers,ou=groups', {'memberUid': memberlist}):
|
||||
if purge:
|
||||
subprocess.call(['rm', '-rf', '/home/{0}'.format(username)])
|
||||
subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)])
|
||||
else:
|
||||
raise MoulinetteError(169, m18n.n('user_deletion_failed'))
|
||||
raise YunohostError('user_deletion_failed')
|
||||
|
||||
app_ssowatconf(auth)
|
||||
|
||||
|
@ -260,8 +264,8 @@ def user_delete(operation_logger, auth, username, purge=False):
|
|||
|
||||
@is_unit_operation([('username', 'user')], exclude=['auth', 'change_password'])
|
||||
def user_update(operation_logger, auth, username, firstname=None, lastname=None, mail=None,
|
||||
change_password=None, add_mailforward=None, remove_mailforward=None,
|
||||
add_mailalias=None, remove_mailalias=None, mailbox_quota=None):
|
||||
change_password=None, add_mailforward=None, remove_mailforward=None,
|
||||
add_mailalias=None, remove_mailalias=None, mailbox_quota=None):
|
||||
"""
|
||||
Update user informations
|
||||
|
||||
|
@ -277,8 +281,9 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
|
|||
remove_mailalias -- Mail aliases to remove
|
||||
|
||||
"""
|
||||
from yunohost.domain import domain_list
|
||||
from yunohost.domain import domain_list, _get_maindomain
|
||||
from yunohost.app import app_ssowatconf
|
||||
from yunohost.utils.password import assert_password_is_strong_enough
|
||||
|
||||
attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop']
|
||||
new_attr_dict = {}
|
||||
|
@ -287,7 +292,7 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
|
|||
# Populate user informations
|
||||
result = auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch)
|
||||
if not result:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
|
||||
raise YunohostError('user_unknown', user=username)
|
||||
user = result[0]
|
||||
|
||||
# Get modifications from arguments
|
||||
|
@ -303,14 +308,25 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
|
|||
new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + lastname
|
||||
|
||||
if change_password:
|
||||
# Ensure sufficiently complex password
|
||||
assert_password_is_strong_enough("user", change_password)
|
||||
|
||||
new_attr_dict['userPassword'] = _hash_user_password(change_password)
|
||||
|
||||
if mail:
|
||||
main_domain = _get_maindomain()
|
||||
aliases = [
|
||||
'root@' + main_domain,
|
||||
'admin@' + main_domain,
|
||||
'webmaster@' + main_domain,
|
||||
'postmaster@' + main_domain,
|
||||
]
|
||||
auth.validate_uniqueness({'mail': mail})
|
||||
if mail[mail.find('@') + 1:] not in domains:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('mail_domain_unknown',
|
||||
domain=mail[mail.find('@') + 1:]))
|
||||
raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:])
|
||||
if mail in aliases:
|
||||
raise YunohostError('mail_unavailable')
|
||||
|
||||
del user['mail'][0]
|
||||
new_attr_dict['mail'] = [mail] + user['mail']
|
||||
|
||||
|
@ -320,9 +336,7 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
|
|||
for mail in add_mailalias:
|
||||
auth.validate_uniqueness({'mail': mail})
|
||||
if mail[mail.find('@') + 1:] not in domains:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('mail_domain_unknown',
|
||||
domain=mail[mail.find('@') + 1:]))
|
||||
raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:])
|
||||
user['mail'].append(mail)
|
||||
new_attr_dict['mail'] = user['mail']
|
||||
|
||||
|
@ -333,8 +347,7 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
|
|||
if len(user['mail']) > 1 and mail in user['mail'][1:]:
|
||||
user['mail'].remove(mail)
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('mail_alias_remove_failed', mail=mail))
|
||||
raise YunohostError('mail_alias_remove_failed', mail=mail)
|
||||
new_attr_dict['mail'] = user['mail']
|
||||
|
||||
if add_mailforward:
|
||||
|
@ -353,8 +366,7 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
|
|||
if len(user['maildrop']) > 1 and mail in user['maildrop'][1:]:
|
||||
user['maildrop'].remove(mail)
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('mail_forward_remove_failed', mail=mail))
|
||||
raise YunohostError('mail_forward_remove_failed', mail=mail)
|
||||
new_attr_dict['maildrop'] = user['maildrop']
|
||||
|
||||
if mailbox_quota is not None:
|
||||
|
@ -367,7 +379,7 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
|
|||
app_ssowatconf(auth)
|
||||
return user_info(auth, username)
|
||||
else:
|
||||
raise MoulinetteError(169, m18n.n('user_update_failed'))
|
||||
raise YunohostError('user_update_failed')
|
||||
|
||||
|
||||
def user_info(auth, username):
|
||||
|
@ -392,7 +404,7 @@ def user_info(auth, username):
|
|||
if result:
|
||||
user = result[0]
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
|
||||
raise YunohostError('user_unknown', user=username)
|
||||
|
||||
result_dict = {
|
||||
'username': user['uid'][0],
|
||||
|
@ -448,7 +460,7 @@ def user_info(auth, username):
|
|||
if result:
|
||||
return result_dict
|
||||
else:
|
||||
raise MoulinetteError(167, m18n.n('user_info_failed'))
|
||||
raise YunohostError('user_info_failed')
|
||||
|
||||
#
|
||||
# SSH subcategory
|
||||
|
@ -456,18 +468,23 @@ def user_info(auth, username):
|
|||
#
|
||||
import yunohost.ssh
|
||||
|
||||
|
||||
def user_ssh_allow(auth, username):
|
||||
return yunohost.ssh.user_ssh_allow(auth, username)
|
||||
|
||||
|
||||
def user_ssh_disallow(auth, username):
|
||||
return yunohost.ssh.user_ssh_disallow(auth, username)
|
||||
|
||||
|
||||
def user_ssh_list_keys(auth, username):
|
||||
return yunohost.ssh.user_ssh_list_keys(auth, username)
|
||||
|
||||
|
||||
def user_ssh_add_key(auth, username, key, comment):
|
||||
return yunohost.ssh.user_ssh_add_key(auth, username, key, comment)
|
||||
|
||||
|
||||
def user_ssh_remove_key(auth, username, key):
|
||||
return yunohost.ssh.user_ssh_remove_key(auth, username, key)
|
||||
|
||||
|
@ -475,6 +492,7 @@ def user_ssh_remove_key(auth, username, key):
|
|||
# End SSH subcategory
|
||||
#
|
||||
|
||||
|
||||
def _convertSize(num, suffix=''):
|
||||
for unit in ['K', 'M', 'G', 'T', 'P', 'E', 'Z']:
|
||||
if abs(num) < 1024.0:
|
||||
|
@ -510,6 +528,3 @@ def _hash_user_password(password):
|
|||
|
||||
salt = '$6$' + salt + '$'
|
||||
return '{CRYPT}' + crypt.crypt(str(password), salt)
|
||||
|
||||
|
||||
|
||||
|
|
40
src/yunohost/utils/error.py
Normal file
40
src/yunohost/utils/error.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" License
|
||||
|
||||
Copyright (C) 2018 YUNOHOST.ORG
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program; if not, see http://www.gnu.org/licenses
|
||||
|
||||
"""
|
||||
|
||||
from moulinette.core import MoulinetteError
|
||||
from moulinette import m18n
|
||||
|
||||
|
||||
class YunohostError(MoulinetteError):
|
||||
|
||||
"""
|
||||
Yunohost base exception
|
||||
|
||||
The (only?) main difference with MoulinetteError being that keys
|
||||
are translated via m18n.n (namespace) instead of m18n.g (global?)
|
||||
"""
|
||||
|
||||
def __init__(self, key, __raw_msg__=False, *args, **kwargs):
|
||||
if __raw_msg__:
|
||||
msg = key
|
||||
else:
|
||||
msg = m18n.n(key, *args, **kwargs)
|
||||
super(YunohostError, self).__init__(msg, __raw_msg__=True)
|
|
@ -20,10 +20,12 @@
|
|||
"""
|
||||
import os
|
||||
|
||||
|
||||
def free_space_in_directory(dirpath):
|
||||
stat = os.statvfs(dirpath)
|
||||
return stat.f_frsize * stat.f_bavail
|
||||
|
||||
|
||||
def space_used_by_directory(dirpath):
|
||||
stat = os.statvfs(dirpath)
|
||||
return stat.f_frsize * stat.f_blocks
|
||||
|
|
|
@ -71,7 +71,7 @@ def get_gateway():
|
|||
return addr.popitem()[1] if len(addr) == 1 else None
|
||||
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
|
||||
|
||||
def _extract_inet(string, skip_netmask=False, skip_loopback=True):
|
||||
|
|
|
@ -33,6 +33,7 @@ logger = logging.getLogger('yunohost.utils.packages')
|
|||
# Exceptions -----------------------------------------------------------------
|
||||
|
||||
class PackageException(Exception):
|
||||
|
||||
"""Base exception related to a package
|
||||
|
||||
Represent an exception related to the package named `pkgname`. If no
|
||||
|
@ -50,16 +51,19 @@ class PackageException(Exception):
|
|||
|
||||
|
||||
class UnknownPackage(PackageException):
|
||||
|
||||
"""The package is not found in the cache."""
|
||||
message_key = 'package_unknown'
|
||||
|
||||
|
||||
class UninstalledPackage(PackageException):
|
||||
|
||||
"""The package is not installed."""
|
||||
message_key = 'package_not_installed'
|
||||
|
||||
|
||||
class InvalidSpecifier(ValueError):
|
||||
|
||||
"""An invalid specifier was found."""
|
||||
|
||||
|
||||
|
@ -68,6 +72,7 @@ class InvalidSpecifier(ValueError):
|
|||
# See: https://github.com/pypa/packaging
|
||||
|
||||
class Specifier(object):
|
||||
|
||||
"""Unique package version specifier
|
||||
|
||||
Restrict a package version according to the `spec`. It must be a string
|
||||
|
@ -257,6 +262,7 @@ class Specifier(object):
|
|||
|
||||
|
||||
class SpecifierSet(object):
|
||||
|
||||
"""A set of package version specifiers
|
||||
|
||||
Combine several Specifier separated by a comma. It allows to restrict
|
||||
|
|
196
src/yunohost/utils/password.py
Normal file
196
src/yunohost/utils/password.py
Normal file
|
@ -0,0 +1,196 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" License
|
||||
|
||||
Copyright (C) 2018 YunoHost
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program; if not, see http://www.gnu.org/licenses
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import string
|
||||
import subprocess
|
||||
|
||||
SMALL_PWD_LIST = ["yunohost", "olinuxino", "olinux", "raspberry", "admin",
|
||||
"root", "test", "rpi"]
|
||||
|
||||
MOST_USED_PASSWORDS = '/usr/share/yunohost/other/password/100000-most-used.txt'
|
||||
|
||||
# Length, digits, lowers, uppers, others
|
||||
STRENGTH_LEVELS = [
|
||||
(8, 0, 0, 0, 0),
|
||||
(8, 1, 1, 1, 0),
|
||||
(8, 1, 1, 1, 1),
|
||||
(12, 1, 1, 1, 1),
|
||||
]
|
||||
|
||||
|
||||
def assert_password_is_strong_enough(profile, password):
|
||||
PasswordValidator(profile).validate(password)
|
||||
|
||||
|
||||
class PasswordValidator(object):
|
||||
|
||||
def __init__(self, profile):
|
||||
"""
|
||||
Initialize a password validator.
|
||||
|
||||
The profile shall be either "user" or "admin"
|
||||
and will correspond to a validation strength
|
||||
defined via the setting "security.password.<profile>.strength"
|
||||
"""
|
||||
|
||||
self.profile = profile
|
||||
try:
|
||||
# We do this "manually" instead of using settings_get()
|
||||
# from settings.py because this file is also meant to be
|
||||
# use as a script by ssowat.
|
||||
# (or at least that's my understanding -- Alex)
|
||||
settings = json.load(open('/etc/yunohost/settings.json', "r"))
|
||||
setting_key = "security.password." + profile + ".strength"
|
||||
self.validation_strength = int(settings[setting_key]["value"])
|
||||
except Exception as e:
|
||||
# Fallback to default value if we can't fetch settings for some reason
|
||||
self.validation_strength = 1
|
||||
|
||||
def validate(self, password):
|
||||
"""
|
||||
Check the validation_summary and trigger an exception
|
||||
if the password does not pass tests.
|
||||
|
||||
This method is meant to be used from inside YunoHost's code
|
||||
(compared to validation_summary which is meant to be called
|
||||
by ssowat)
|
||||
"""
|
||||
if self.validation_strength == -1:
|
||||
return
|
||||
|
||||
# Note that those imports are made here and can't be put
|
||||
# on top (at least not the moulinette ones)
|
||||
# because the moulinette needs to be correctly initialized
|
||||
# as well as modules available in python's path.
|
||||
import logging
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
logger = logging.getLogger('yunohost.utils.password')
|
||||
|
||||
status, msg = self.validation_summary(password)
|
||||
if status == "error":
|
||||
raise YunohostError(msg)
|
||||
|
||||
def validation_summary(self, password):
|
||||
"""
|
||||
Check if a password is listed in the list of most used password
|
||||
and if the overall strength is good enough compared to the
|
||||
validation_strength defined in the constructor.
|
||||
|
||||
Produces a summary-tuple comprised of a level (succes or error)
|
||||
and a message key describing the issues found.
|
||||
"""
|
||||
if self.validation_strength < 0:
|
||||
return ("success", "")
|
||||
|
||||
listed = password in SMALL_PWD_LIST or self.is_in_most_used_list(password)
|
||||
strength_level = self.strength_level(password)
|
||||
if listed:
|
||||
return ("error", "password_listed")
|
||||
if strength_level < self.validation_strength:
|
||||
return ("error", "password_too_simple_%s" % self.validation_strength)
|
||||
|
||||
return ("success", "")
|
||||
|
||||
def strength(self, password):
|
||||
"""
|
||||
Returns the strength of a password, defined as a tuple
|
||||
containing the length of the password, the number of digits,
|
||||
lowercase letters, uppercase letters, and other characters.
|
||||
|
||||
For instance, "PikachuDu67" is (11, 2, 7, 2, 0)
|
||||
"""
|
||||
|
||||
length = len(password)
|
||||
digits = 0
|
||||
uppers = 0
|
||||
lowers = 0
|
||||
others = 0
|
||||
|
||||
for character in password:
|
||||
if character in string.digits:
|
||||
digits = digits + 1
|
||||
elif character in string.ascii_uppercase:
|
||||
uppers = uppers + 1
|
||||
elif character in string.ascii_lowercase:
|
||||
lowers = lowers + 1
|
||||
else:
|
||||
others = others + 1
|
||||
|
||||
return (length, digits, lowers, uppers, others)
|
||||
|
||||
def strength_level(self, password):
|
||||
"""
|
||||
Computes the strength of a password and compares
|
||||
it to the STRENGTH_LEVELS.
|
||||
|
||||
Returns an int corresponding to the highest STRENGTH_LEVEL
|
||||
satisfied by the password.
|
||||
"""
|
||||
|
||||
strength = self.strength(password)
|
||||
|
||||
strength_level = 0
|
||||
# Iterate over each level and its criterias
|
||||
for level, level_criterias in enumerate(STRENGTH_LEVELS):
|
||||
# Iterate simulatenously over the level criterias (e.g. [8, 1, 1, 1, 0])
|
||||
# and the strength of the password (e.g. [11, 2, 7, 2, 0])
|
||||
# and compare the values 1-by-1.
|
||||
# If one False is found, the password does not satisfy the level
|
||||
if False in [s >= c for s, c in zip(strength, level_criterias)]:
|
||||
break
|
||||
# Otherwise, the strength of the password is at least of the current level.
|
||||
strength_level = level + 1
|
||||
|
||||
return strength_level
|
||||
|
||||
def is_in_most_used_list(self, password):
|
||||
|
||||
# Decompress file if compressed
|
||||
if os.path.exists("%s.gz" % MOST_USED_PASSWORDS):
|
||||
os.system("gzip -fd %s.gz" % MOST_USED_PASSWORDS)
|
||||
|
||||
# Grep the password in the file
|
||||
# We use '-f -' to feed the pattern (= the password) through
|
||||
# stdin to avoid it being shown in ps -ef --forest...
|
||||
command = "grep -q -f - %s" % MOST_USED_PASSWORDS
|
||||
p = subprocess.Popen(command.split(), stdin=subprocess.PIPE)
|
||||
p.communicate(input=password)
|
||||
return not bool(p.returncode)
|
||||
|
||||
|
||||
# This file is also meant to be used as an executable by
|
||||
# SSOwat to validate password from the portal when an user
|
||||
# change its password.
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
import getpass
|
||||
pwd = getpass.getpass("")
|
||||
# print("usage: password.py PASSWORD")
|
||||
else:
|
||||
pwd = sys.argv[1]
|
||||
status, msg = PasswordValidator('user').validation_summary(pwd)
|
||||
print(msg)
|
||||
sys.exit(0)
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
import requests
|
||||
import json
|
||||
import errno
|
||||
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
|
||||
|
||||
def yunopaste(data):
|
||||
|
||||
|
@ -13,17 +13,14 @@ def yunopaste(data):
|
|||
try:
|
||||
r = requests.post("%s/documents" % paste_server, data=data, timeout=30)
|
||||
except Exception as e:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
"Something wrong happened while trying to paste data on paste.yunohost.org : %s" % str(e))
|
||||
raise YunohostError("Something wrong happened while trying to paste data on paste.yunohost.org : %s" % str(e), raw_msg=True)
|
||||
|
||||
if r.status_code != 200:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
"Something wrong happened while trying to paste data on paste.yunohost.org : %s, %s" % (r.status_code, r.text))
|
||||
raise YunohostError("Something wrong happened while trying to paste data on paste.yunohost.org : %s, %s" % (r.status_code, r.text), raw_msg=True)
|
||||
|
||||
try:
|
||||
url = json.loads(r.text)["key"]
|
||||
except:
|
||||
raise MoulinetteError(errno.EIO,
|
||||
"Uhoh, couldn't parse the answer from paste.yunohost.org : %s" % r.text)
|
||||
raise YunohostError("Uhoh, couldn't parse the answer from paste.yunohost.org : %s" % r.text, raw_msg=True)
|
||||
|
||||
return "%s/raw/%s" % (paste_server, url)
|
||||
|
|
Loading…
Add table
Reference in a new issue