mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge branch 'stretch-unstable' into migrate-pwd
This commit is contained in:
commit
fb2d85b90d
37 changed files with 1178 additions and 279 deletions
|
@ -56,7 +56,7 @@ else
|
||||||
echo "$LOGO_AND_FINGERPRINTS"
|
echo "$LOGO_AND_FINGERPRINTS"
|
||||||
echo -e "\e[m Post-installation \e[0m"
|
echo -e "\e[m Post-installation \e[0m"
|
||||||
echo "Congratulations! YunoHost has been successfully installed.\nTwo more steps are required to activate the services of your server."
|
echo "Congratulations! YunoHost has been successfully installed.\nTwo more steps are required to activate the services of your server."
|
||||||
read -p "Proceed to post-installation? (y/n) " -n 1
|
read -p "Proceed to post-installation? (y/n)\nAlternatively, you can proceed the post-installation on https://${ip}" -n 1
|
||||||
RESULT=1
|
RESULT=1
|
||||||
while [ $RESULT -gt 0 ]; do
|
while [ $RESULT -gt 0 ]; do
|
||||||
if [[ $REPLY =~ ^[Nn]$ ]]; then
|
if [[ $REPLY =~ ^[Nn]$ ]]; then
|
||||||
|
|
|
@ -105,7 +105,7 @@ user:
|
||||||
ask: ask_lastname
|
ask: ask_lastname
|
||||||
required: True
|
required: True
|
||||||
pattern: &pattern_lastname
|
pattern: &pattern_lastname
|
||||||
- !!str ^([^\W\d_]{2,30}[ ,.']{0,3})+$
|
- !!str ^([^\W\d_]{2,30}[ ,.'-]{0,3})+$
|
||||||
- "pattern_lastname"
|
- "pattern_lastname"
|
||||||
-m:
|
-m:
|
||||||
full: --mail
|
full: --mail
|
||||||
|
@ -125,6 +125,7 @@ user:
|
||||||
pattern: &pattern_password
|
pattern: &pattern_password
|
||||||
- !!str ^.{3,}$
|
- !!str ^.{3,}$
|
||||||
- "pattern_password"
|
- "pattern_password"
|
||||||
|
comment: good_practices_about_user_password
|
||||||
-q:
|
-q:
|
||||||
full: --mailbox-quota
|
full: --mailbox-quota
|
||||||
help: Mailbox size quota
|
help: Mailbox size quota
|
||||||
|
@ -780,18 +781,18 @@ app:
|
||||||
### app_action_list()
|
### app_action_list()
|
||||||
list:
|
list:
|
||||||
action_help: List app actions
|
action_help: List app actions
|
||||||
api: GET /apps/<app_id>/actions
|
api: GET /apps/<app>/actions
|
||||||
arguments:
|
arguments:
|
||||||
app_id:
|
app:
|
||||||
help: app id
|
help: App name
|
||||||
|
|
||||||
### app_action_run()
|
### app_action_run()
|
||||||
run:
|
run:
|
||||||
action_help: Run app action
|
action_help: Run app action
|
||||||
api: PUT /apps/<app_id>/actions/<action>
|
api: PUT /apps/<app>/actions/<action>
|
||||||
arguments:
|
arguments:
|
||||||
app_id:
|
app:
|
||||||
help: app id
|
help: App name
|
||||||
action:
|
action:
|
||||||
help: action id
|
help: action id
|
||||||
-a:
|
-a:
|
||||||
|
@ -805,18 +806,18 @@ app:
|
||||||
### app_config_show_panel()
|
### app_config_show_panel()
|
||||||
show-panel:
|
show-panel:
|
||||||
action_help: show config panel for the application
|
action_help: show config panel for the application
|
||||||
api: GET /apps/<app_id>/config-panel
|
api: GET /apps/<app>/config-panel
|
||||||
arguments:
|
arguments:
|
||||||
app_id:
|
app:
|
||||||
help: App ID
|
help: App name
|
||||||
|
|
||||||
### app_config_apply()
|
### app_config_apply()
|
||||||
apply:
|
apply:
|
||||||
action_help: apply the new configuration
|
action_help: apply the new configuration
|
||||||
api: POST /apps/<app_id>/config
|
api: POST /apps/<app>/config
|
||||||
arguments:
|
arguments:
|
||||||
app_id:
|
app:
|
||||||
help: App ID
|
help: App name
|
||||||
-a:
|
-a:
|
||||||
full: --args
|
full: --args
|
||||||
help: Serialized arguments for new configuration (i.e. "domain=domain.tld&path=/path")
|
help: Serialized arguments for new configuration (i.e. "domain=domain.tld&path=/path")
|
||||||
|
@ -1136,6 +1137,13 @@ service:
|
||||||
full: --runlevel
|
full: --runlevel
|
||||||
help: Runlevel priority of the service
|
help: Runlevel priority of the service
|
||||||
type: int
|
type: int
|
||||||
|
-n:
|
||||||
|
full: --need_lock
|
||||||
|
help: Use this option to prevent deadlocks if the service does invoke yunohost commands.
|
||||||
|
action: store_true
|
||||||
|
-d:
|
||||||
|
full: --description
|
||||||
|
help: Description of the service
|
||||||
|
|
||||||
### service_remove()
|
### service_remove()
|
||||||
remove:
|
remove:
|
||||||
|
@ -1449,6 +1457,7 @@ tools:
|
||||||
password: ask_new_admin_password
|
password: ask_new_admin_password
|
||||||
pattern: *pattern_password
|
pattern: *pattern_password
|
||||||
required: True
|
required: True
|
||||||
|
comment: good_practices_about_admin_password
|
||||||
|
|
||||||
### tools_maindomain()
|
### tools_maindomain()
|
||||||
maindomain:
|
maindomain:
|
||||||
|
@ -1486,9 +1495,13 @@ tools:
|
||||||
password: ask_new_admin_password
|
password: ask_new_admin_password
|
||||||
pattern: *pattern_password
|
pattern: *pattern_password
|
||||||
required: True
|
required: True
|
||||||
|
comment: good_practices_about_admin_password
|
||||||
--ignore-dyndns:
|
--ignore-dyndns:
|
||||||
help: Do not subscribe domain to a DynDNS service
|
help: Do not subscribe domain to a DynDNS service
|
||||||
action: store_true
|
action: store_true
|
||||||
|
--force-password:
|
||||||
|
help: Use this if you really want to set a weak password
|
||||||
|
action: store_true
|
||||||
|
|
||||||
### tools_update()
|
### tools_update()
|
||||||
update:
|
update:
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
# Use logrotate to manage the logfile
|
# Use logrotate to manage the logfile
|
||||||
#
|
#
|
||||||
# usage: ynh_use_logrotate [logfile] [--non-append]
|
# usage: ynh_use_logrotate [logfile] [--non-append|--append] [specific_user/specific_group]
|
||||||
# | arg: logfile - absolute path of logfile
|
# | arg: logfile - absolute path of logfile
|
||||||
# | arg: --non-append - (Option) Replace the config file instead of appending this new config.
|
# | arg: --non-append - (Option) Replace the config file instead of appending this new config.
|
||||||
|
# | arg: specific_user : run logrotate as the specified user and group. If not specified logrotate is runned as root.
|
||||||
#
|
#
|
||||||
# If no argument provided, a standard directory will be use. /var/log/${app}
|
# If no argument provided, a standard directory will be use. /var/log/${app}
|
||||||
# You can provide a path with the directory only or with the logfile.
|
# You can provide a path with the directory only or with the logfile.
|
||||||
|
@ -13,6 +14,7 @@
|
||||||
# Unless you use the option --non-append
|
# Unless you use the option --non-append
|
||||||
ynh_use_logrotate () {
|
ynh_use_logrotate () {
|
||||||
local customtee="tee -a"
|
local customtee="tee -a"
|
||||||
|
local user_group="${3:-}"
|
||||||
if [ $# -gt 0 ] && [ "$1" == "--non-append" ]; then
|
if [ $# -gt 0 ] && [ "$1" == "--non-append" ]; then
|
||||||
customtee="tee"
|
customtee="tee"
|
||||||
# Destroy this argument for the next command.
|
# Destroy this argument for the next command.
|
||||||
|
@ -29,6 +31,12 @@ ynh_use_logrotate () {
|
||||||
else
|
else
|
||||||
local logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log
|
local logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log
|
||||||
fi
|
fi
|
||||||
|
local su_directive=""
|
||||||
|
if [[ -n $user_group ]]; then
|
||||||
|
su_directive=" # Run logorotate as specific user - group
|
||||||
|
su ${user_group%/*} ${user_group#*/}"
|
||||||
|
fi
|
||||||
|
|
||||||
cat > ./${app}-logrotate << EOF # Build a config file for logrotate
|
cat > ./${app}-logrotate << EOF # Build a config file for logrotate
|
||||||
$logfile {
|
$logfile {
|
||||||
# Rotate if the logfile exceeds 100Mo
|
# Rotate if the logfile exceeds 100Mo
|
||||||
|
@ -47,6 +55,7 @@ $logfile {
|
||||||
notifempty
|
notifempty
|
||||||
# Keep old logs in the same dir
|
# Keep old logs in the same dir
|
||||||
noolddir
|
noolddir
|
||||||
|
$su_directive
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
sudo mkdir -p $(dirname "$logfile") # Create the log directory, if not exist
|
sudo mkdir -p $(dirname "$logfile") # Create the log directory, if not exist
|
||||||
|
|
|
@ -299,6 +299,23 @@ ynh_backup_if_checksum_is_different () {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Delete a file checksum from the app settings
|
||||||
|
#
|
||||||
|
# $app should be defined when calling this helper
|
||||||
|
#
|
||||||
|
# usage: ynh_remove_file_checksum file
|
||||||
|
# | arg: -f, --file= - The file for which the checksum will be deleted
|
||||||
|
ynh_delete_file_checksum () {
|
||||||
|
# Declare an array to define the options of this helper.
|
||||||
|
declare -Ar args_array=( [f]=file= )
|
||||||
|
local file
|
||||||
|
# Manage arguments with getopts
|
||||||
|
ynh_handle_getopts_args "$@"
|
||||||
|
|
||||||
|
local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_'
|
||||||
|
ynh_app_setting_delete $app $checksum_setting_name
|
||||||
|
}
|
||||||
|
|
||||||
# Remove a file or a directory securely
|
# Remove a file or a directory securely
|
||||||
#
|
#
|
||||||
# usage: ynh_secure_remove path_to_remove
|
# usage: ynh_secure_remove path_to_remove
|
||||||
|
|
194
data/helpers.d/getopts
Normal file
194
data/helpers.d/getopts
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Internal helper design to allow helpers to use getopts to manage their arguments
|
||||||
|
#
|
||||||
|
# [internal]
|
||||||
|
#
|
||||||
|
# example: function my_helper()
|
||||||
|
# {
|
||||||
|
# declare -Ar args_array=( [a]=arg1= [b]=arg2= [c]=arg3 )
|
||||||
|
# local arg1
|
||||||
|
# local arg2
|
||||||
|
# local arg3
|
||||||
|
# ynh_handle_getopts_args "$@"
|
||||||
|
#
|
||||||
|
# [...]
|
||||||
|
# }
|
||||||
|
# my_helper --arg1 "val1" -b val2 -c
|
||||||
|
#
|
||||||
|
# usage: ynh_handle_getopts_args "$@"
|
||||||
|
# | arg: $@ - Simply "$@" to tranfert all the positionnal arguments to the function
|
||||||
|
#
|
||||||
|
# This helper need an array, named "args_array" with all the arguments used by the helper
|
||||||
|
# that want to use ynh_handle_getopts_args
|
||||||
|
# Be carreful, this array has to be an associative array, as the following example:
|
||||||
|
# declare -Ar args_array=( [a]=arg1 [b]=arg2= [c]=arg3 )
|
||||||
|
# Let's explain this array:
|
||||||
|
# a, b and c are short options, -a, -b and -c
|
||||||
|
# arg1, arg2 and arg3 are the long options associated to the previous short ones. --arg1, --arg2 and --arg3
|
||||||
|
# For each option, a short and long version has to be defined.
|
||||||
|
# Let's see something more significant
|
||||||
|
# declare -Ar args_array=( [u]=user [f]=finalpath= [d]=database )
|
||||||
|
#
|
||||||
|
# NB: Because we're using 'declare' without -g, the array will be declared as a local variable.
|
||||||
|
#
|
||||||
|
# Please keep in mind that the long option will be used as a variable to store the values for this option.
|
||||||
|
# For the previous example, that means that $finalpath will be fill with the value given as argument for this option.
|
||||||
|
#
|
||||||
|
# Also, in the previous example, finalpath has a '=' at the end. That means this option need a value.
|
||||||
|
# So, the helper has to be call with --finalpath /final/path, --finalpath=/final/path or -f /final/path, the variable $finalpath will get the value /final/path
|
||||||
|
# If there's many values for an option, -f /final /path, the value will be separated by a ';' $finalpath=/final;/path
|
||||||
|
# For an option without value, like --user in the example, the helper can be called only with --user or -u. $user will then get the value 1.
|
||||||
|
#
|
||||||
|
# To keep a retrocompatibility, a package can still call a helper, using getopts, with positional arguments.
|
||||||
|
# The "legacy mode" will manage the positional arguments and fill the variable in the same order than they are given in $args_array.
|
||||||
|
# e.g. for `my_helper "val1" val2`, arg1 will be filled with val1, and arg2 with val2.
|
||||||
|
ynh_handle_getopts_args () {
|
||||||
|
# Manage arguments only if there's some provided
|
||||||
|
set +x
|
||||||
|
if [ $# -ne 0 ]
|
||||||
|
then
|
||||||
|
# Store arguments in an array to keep each argument separated
|
||||||
|
local arguments=("$@")
|
||||||
|
|
||||||
|
# For each option in the array, reduce to short options for getopts (e.g. for [u]=user, --user will be -u)
|
||||||
|
# And built parameters string for getopts
|
||||||
|
# ${!args_array[@]} is the list of all keys in the array (A key is 'u' in [u]=user, user is a value)
|
||||||
|
local getopts_parameters=""
|
||||||
|
local key=""
|
||||||
|
for key in "${!args_array[@]}"
|
||||||
|
do
|
||||||
|
# Concatenate each keys of the array to build the string of arguments for getopts
|
||||||
|
# Will looks like 'abcd' for -a -b -c -d
|
||||||
|
# If the value of a key finish by =, it's an option with additionnal values. (e.g. --user bob or -u bob)
|
||||||
|
# Check the last character of the value associate to the key
|
||||||
|
if [ "${args_array[$key]: -1}" = "=" ]
|
||||||
|
then
|
||||||
|
# For an option with additionnal values, add a ':' after the letter for getopts.
|
||||||
|
getopts_parameters="${getopts_parameters}${key}:"
|
||||||
|
else
|
||||||
|
getopts_parameters="${getopts_parameters}${key}"
|
||||||
|
fi
|
||||||
|
# Check each argument given to the function
|
||||||
|
local arg=""
|
||||||
|
# ${#arguments[@]} is the size of the array
|
||||||
|
for arg in `seq 0 $(( ${#arguments[@]} - 1 ))`
|
||||||
|
do
|
||||||
|
# And replace long option (value of the key) by the short option, the key itself
|
||||||
|
# (e.g. for [u]=user, --user will be -u)
|
||||||
|
# Replace long option with =
|
||||||
|
arguments[arg]="${arguments[arg]//--${args_array[$key]}/-${key} }"
|
||||||
|
# And long option without =
|
||||||
|
arguments[arg]="${arguments[arg]//--${args_array[$key]%=}/-${key}}"
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
# Read and parse all the arguments
|
||||||
|
# Use a function here, to use standart arguments $@ and be able to use shift.
|
||||||
|
parse_arg () {
|
||||||
|
# Read all arguments, until no arguments are left
|
||||||
|
while [ $# -ne 0 ]
|
||||||
|
do
|
||||||
|
# Initialize the index of getopts
|
||||||
|
OPTIND=1
|
||||||
|
# Parse with getopts only if the argument begin by -, that means the argument is an option
|
||||||
|
# getopts will fill $parameter with the letter of the option it has read.
|
||||||
|
local parameter=""
|
||||||
|
getopts ":$getopts_parameters" parameter || true
|
||||||
|
|
||||||
|
if [ "$parameter" = "?" ]
|
||||||
|
then
|
||||||
|
ynh_die "Invalid argument: -${OPTARG:-}"
|
||||||
|
elif [ "$parameter" = ":" ]
|
||||||
|
then
|
||||||
|
ynh_die "-$OPTARG parameter requires an argument."
|
||||||
|
else
|
||||||
|
local shift_value=1
|
||||||
|
# Use the long option, corresponding to the short option read by getopts, as a variable
|
||||||
|
# (e.g. for [u]=user, 'user' will be used as a variable)
|
||||||
|
# Also, remove '=' at the end of the long option
|
||||||
|
# The variable name will be stored in 'option_var'
|
||||||
|
local option_var="${args_array[$parameter]%=}"
|
||||||
|
# If this option doesn't take values
|
||||||
|
# if there's a '=' at the end of the long option name, this option takes values
|
||||||
|
if [ "${args_array[$parameter]: -1}" != "=" ]
|
||||||
|
then
|
||||||
|
# 'eval ${option_var}' will use the content of 'option_var'
|
||||||
|
eval ${option_var}=1
|
||||||
|
else
|
||||||
|
# Read all other arguments to find multiple value for this option.
|
||||||
|
# Load args in a array
|
||||||
|
local all_args=("$@")
|
||||||
|
|
||||||
|
# If the first argument is longer than 2 characters,
|
||||||
|
# There's a value attached to the option, in the same array cell
|
||||||
|
if [ ${#all_args[0]} -gt 2 ]; then
|
||||||
|
# Remove the option and the space, so keep only the value itself.
|
||||||
|
all_args[0]="${all_args[0]#-${parameter} }"
|
||||||
|
# Reduce the value of shift, because the option has been removed manually
|
||||||
|
shift_value=$(( shift_value - 1 ))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Then read the array value per value
|
||||||
|
for i in `seq 0 $(( ${#all_args[@]} - 1 ))`
|
||||||
|
do
|
||||||
|
# If this argument is an option, end here.
|
||||||
|
if [ "${all_args[$i]:0:1}" == "-" ] || [ -z "${all_args[$i]}" ]
|
||||||
|
then
|
||||||
|
# Ignore the first value of the array, which is the option itself
|
||||||
|
if [ "$i" -ne 0 ]; then
|
||||||
|
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}" ]
|
||||||
|
then
|
||||||
|
# If there's already another value for this option, add a ; before adding the new value
|
||||||
|
eval ${option_var}+="\;"
|
||||||
|
fi
|
||||||
|
eval ${option_var}+=\"${all_args[$i]}\"
|
||||||
|
shift_value=$(( shift_value + 1 ))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Shift the parameter and its argument(s)
|
||||||
|
shift $shift_value
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# LEGACY MODE
|
||||||
|
# Check if there's getopts arguments
|
||||||
|
if [ "${arguments[0]:0:1}" != "-" ]
|
||||||
|
then
|
||||||
|
# If not, enter in legacy mode and manage the arguments as positionnal ones.
|
||||||
|
echo "! Helper used in legacy mode !"
|
||||||
|
for i in `seq 0 $(( ${#arguments[@]} -1 ))`
|
||||||
|
do
|
||||||
|
# Use getopts_parameters as a list of key of the array args_array
|
||||||
|
# Remove all ':' in getopts_parameters
|
||||||
|
getopts_parameters=${getopts_parameters//:}
|
||||||
|
# Get the key from getopts_parameters, by using the key according to the position of the argument.
|
||||||
|
key=${getopts_parameters:$i:1}
|
||||||
|
# Use the long option, corresponding to the key, as a variable
|
||||||
|
# (e.g. for [u]=user, 'user' will be used as a variable)
|
||||||
|
# Also, remove '=' at the end of the long option
|
||||||
|
# The variable name will be stored in 'option_var'
|
||||||
|
local option_var="${args_array[$key]%=}"
|
||||||
|
|
||||||
|
# Store each value given as argument in the corresponding variable
|
||||||
|
# The values will be stored in the same order than $args_array
|
||||||
|
eval ${option_var}+=\"${arguments[$i]}\"
|
||||||
|
done
|
||||||
|
else
|
||||||
|
# END LEGACY MODE
|
||||||
|
# Call parse_arg and pass the modified list of args as an array of arguments.
|
||||||
|
parse_arg "${arguments[@]}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
set -x
|
||||||
|
}
|
|
@ -8,12 +8,11 @@ ynh_die() {
|
||||||
# Display a message in the 'INFO' logging category
|
# Display a message in the 'INFO' logging category
|
||||||
#
|
#
|
||||||
# usage: ynh_info "Some message"
|
# usage: ynh_info "Some message"
|
||||||
ynh_info()
|
ynh_print_info() {
|
||||||
{
|
echo "$1" >> "$YNH_STDINFO"
|
||||||
echo "$1" >>"$YNH_STDINFO"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Ignore the yunohost-cli log to prevent errors with conditionals commands
|
# Ignore the yunohost-cli log to prevent errors with conditional commands
|
||||||
#
|
#
|
||||||
# [internal]
|
# [internal]
|
||||||
#
|
#
|
||||||
|
@ -29,3 +28,99 @@ ynh_no_log() {
|
||||||
sudo mv ${ynh_cli_log}-move ${ynh_cli_log}
|
sudo mv ${ynh_cli_log}-move ${ynh_cli_log}
|
||||||
return $?
|
return $?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Main printer, just in case in the future we have to change anything about that.
|
||||||
|
#
|
||||||
|
# [internal]
|
||||||
|
#
|
||||||
|
ynh_print_log () {
|
||||||
|
echo -e "${1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print a warning on stderr
|
||||||
|
#
|
||||||
|
# usage: ynh_print_warn "Text to print"
|
||||||
|
# | arg: text - The text to print
|
||||||
|
ynh_print_warn () {
|
||||||
|
ynh_print_log "\e[93m\e[1m[WARN]\e[0m ${1}" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print an error on stderr
|
||||||
|
#
|
||||||
|
# usage: ynh_print_err "Text to print"
|
||||||
|
# | arg: text - The text to print
|
||||||
|
ynh_print_err () {
|
||||||
|
ynh_print_log "\e[91m\e[1m[ERR]\e[0m ${1}" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute a command and print the result as an error
|
||||||
|
#
|
||||||
|
# usage: ynh_exec_err command to execute
|
||||||
|
# usage: ynh_exec_err "command to execute | following command"
|
||||||
|
# In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe.
|
||||||
|
#
|
||||||
|
# | arg: command - command to execute
|
||||||
|
ynh_exec_err () {
|
||||||
|
ynh_print_err "$(eval $@)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute a command and print the result as a warning
|
||||||
|
#
|
||||||
|
# usage: ynh_exec_warn command to execute
|
||||||
|
# usage: ynh_exec_warn "command to execute | following command"
|
||||||
|
# In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe.
|
||||||
|
#
|
||||||
|
# | arg: command - command to execute
|
||||||
|
ynh_exec_warn () {
|
||||||
|
ynh_print_warn "$(eval $@)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute a command and force the result to be printed on stdout
|
||||||
|
#
|
||||||
|
# usage: ynh_exec_warn_less command to execute
|
||||||
|
# usage: ynh_exec_warn_less "command to execute | following command"
|
||||||
|
# In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe.
|
||||||
|
#
|
||||||
|
# | arg: command - command to execute
|
||||||
|
ynh_exec_warn_less () {
|
||||||
|
eval $@ 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute a command and redirect stdout in /dev/null
|
||||||
|
#
|
||||||
|
# usage: ynh_exec_quiet command to execute
|
||||||
|
# usage: ynh_exec_quiet "command to execute | following command"
|
||||||
|
# In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe.
|
||||||
|
#
|
||||||
|
# | arg: command - command to execute
|
||||||
|
ynh_exec_quiet () {
|
||||||
|
eval $@ > /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute a command and redirect stdout and stderr in /dev/null
|
||||||
|
#
|
||||||
|
# usage: ynh_exec_fully_quiet command to execute
|
||||||
|
# usage: ynh_exec_fully_quiet "command to execute | following command"
|
||||||
|
# In case of use of pipes, you have to use double quotes. Otherwise, this helper will be executed with the first command, then be sent to the next pipe.
|
||||||
|
#
|
||||||
|
# | arg: command - command to execute
|
||||||
|
ynh_exec_fully_quiet () {
|
||||||
|
eval $@ > /dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Remove any logs for all the following commands.
|
||||||
|
#
|
||||||
|
# usage: ynh_print_OFF
|
||||||
|
# WARNING: You should be careful with this helper, and never forget to use ynh_print_ON as soon as possible to restore the logging.
|
||||||
|
ynh_print_OFF () {
|
||||||
|
set +x
|
||||||
|
}
|
||||||
|
|
||||||
|
# Restore the logging after ynh_print_OFF
|
||||||
|
#
|
||||||
|
# usage: ynh_print_ON
|
||||||
|
ynh_print_ON () {
|
||||||
|
set -x
|
||||||
|
# Print an echo only for the log, to be able to know that ynh_print_ON has been called.
|
||||||
|
echo ynh_print_ON > /dev/null
|
||||||
|
}
|
||||||
|
|
|
@ -173,7 +173,7 @@ ynh_setup_source () {
|
||||||
then # Use the local source file if it is present
|
then # Use the local source file if it is present
|
||||||
cp $local_src $src_filename
|
cp $local_src $src_filename
|
||||||
else # If not, download the source
|
else # If not, download the source
|
||||||
wget -nv -O $src_filename $src_url
|
local out=`wget -nv -O $src_filename $src_url 2>&1` || ynh_print_err $out
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check the control sum
|
# Check the control sum
|
||||||
|
@ -254,6 +254,9 @@ ynh_local_curl () {
|
||||||
POST_data="--data ${POST_data::-1}"
|
POST_data="--data ${POST_data::-1}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Wait untils nginx has fully reloaded (avoid curl fail with http2)
|
||||||
|
sleep 2
|
||||||
|
|
||||||
# Curl the URL
|
# Curl the URL
|
||||||
curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url"
|
curl --silent --show-error -kL -H "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url"
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,8 @@ backup_dir="${1}/data/home"
|
||||||
# Backup user home
|
# Backup user home
|
||||||
for f in $(find /home/* -type d -prune | awk -F/ '{print $NF}'); do
|
for f in $(find /home/* -type d -prune | awk -F/ '{print $NF}'); do
|
||||||
if [[ ! "$f" =~ ^yunohost|lost\+found ]]; then
|
if [[ ! "$f" =~ ^yunohost|lost\+found ]]; then
|
||||||
|
if [ ! -e "/home/$f/.nobackup" ]; then
|
||||||
ynh_backup "/home/$f" "${backup_dir}/$f" 1
|
ynh_backup "/home/$f" "${backup_dir}/$f" 1
|
||||||
fi
|
fi
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
. /usr/share/yunohost/helpers.d/utils
|
||||||
|
|
||||||
do_init_regen() {
|
do_init_regen() {
|
||||||
if [[ $EUID -ne 0 ]]; then
|
if [[ $EUID -ne 0 ]]; then
|
||||||
echo "You must be root to run this script" 1>&2
|
echo "You must be root to run this script" 1>&2
|
||||||
|
@ -42,18 +44,18 @@ do_pre_regen() {
|
||||||
mkdir -p "$mail_autoconfig_dir"
|
mkdir -p "$mail_autoconfig_dir"
|
||||||
|
|
||||||
# NGINX server configuration
|
# NGINX server configuration
|
||||||
cat server.tpl.conf \
|
export domain
|
||||||
| sed "s/{{ domain }}/${domain}/g" \
|
export domain_cert_ca=$(yunohost domain cert-status $domain --json \
|
||||||
> "${nginx_conf_dir}/${domain}.conf"
|
| jq ".certificates.\"$domain\".CA_type" \
|
||||||
|
| tr -d '"')
|
||||||
cat autoconfig.tpl.xml \
|
|
||||||
| sed "s/{{ domain }}/${domain}/g" \
|
|
||||||
> "${mail_autoconfig_dir}/config-v1.1.xml"
|
|
||||||
|
|
||||||
|
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 ]] \
|
[[ $main_domain != $domain ]] \
|
||||||
&& touch "${domain_conf_dir}/yunohost_local.conf" \
|
&& touch "${domain_conf_dir}/yunohost_local.conf" \
|
||||||
|| cp yunohost_local.conf "${domain_conf_dir}/yunohost_local.conf"
|
|| cp yunohost_local.conf "${domain_conf_dir}/yunohost_local.conf"
|
||||||
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# remove old domain conf files
|
# 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
|
-- A table is a list of values, except each value has a name. An
|
||||||
-- example would be:
|
-- 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
|
-- Tip: You can check that the syntax of this file is correct when you have finished
|
||||||
-- by running: luac -p metronome.cfg.lua
|
-- by running: luac -p metronome.cfg.lua
|
||||||
-- If there are any errors, it will let you know what and where they are, otherwise it
|
-- If there are any errors, it will let you know what and where they are, otherwise it
|
||||||
-- will keep quiet.
|
-- will keep quiet.
|
||||||
|
|
||||||
---------- Server-wide settings ----------
|
-- Global settings go in this section
|
||||||
-- Settings in this section apply to the whole server and are the default settings
|
|
||||||
-- for any virtual hosts
|
-- 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
|
-- Server PID
|
||||||
pidfile = "/var/run/metronome/metronome.pid"
|
pidfile = "/var/run/metronome/metronome.pid"
|
||||||
|
@ -33,109 +81,25 @@ http_interfaces = { "127.0.0.1", "::1" }
|
||||||
-- Enable IPv6
|
-- Enable IPv6
|
||||||
use_ipv6 = true
|
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
|
-- Discovery items
|
||||||
disco_items = {
|
disco_items = {
|
||||||
{ "muc.{{ main_domain }}" },
|
{ "muc.{{ main_domain }}" },
|
||||||
{ "pubsub.{{ main_domain }}" },
|
{ "pubsub.{{ main_domain }}" },
|
||||||
|
{ "upload.{{ main_domain }}" },
|
||||||
{ "vjud.{{ main_domain }}" }
|
{ "vjud.{{ main_domain }}" }
|
||||||
};
|
};
|
||||||
|
|
||||||
-- BOSH configuration (mod_bosh)
|
-- BOSH configuration (mod_bosh)
|
||||||
bosh_max_inactivity = 30
|
|
||||||
consider_bosh_secure = true
|
consider_bosh_secure = true
|
||||||
cross_domain_bosh = true
|
cross_domain_bosh = true
|
||||||
|
|
||||||
|
-- WebSocket configuration (mod_websocket)
|
||||||
|
consider_websocket_secure = true
|
||||||
|
cross_domain_websocket = true
|
||||||
|
|
||||||
-- Disable account creation by default, for security
|
-- Disable account creation by default, for security
|
||||||
allow_registration = false
|
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
|
-- Use LDAP storage backend for all stores
|
||||||
storage = "ldap"
|
storage = "ldap"
|
||||||
|
|
||||||
|
@ -147,7 +111,6 @@ log = {
|
||||||
-- "*console"; -- Log to the console, useful for debugging with daemonize=false
|
-- "*console"; -- Log to the console, useful for debugging with daemonize=false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
------ Components ------
|
------ Components ------
|
||||||
-- You can specify components to add hosts that provide special services,
|
-- You can specify components to add hosts that provide special services,
|
||||||
-- like multi-user conferences, and transports.
|
-- like multi-user conferences, and transports.
|
||||||
|
@ -163,23 +126,28 @@ Component "muc.{{ main_domain }}" "muc"
|
||||||
modules_enabled = {
|
modules_enabled = {
|
||||||
"muc_limits";
|
"muc_limits";
|
||||||
"muc_log";
|
"muc_log";
|
||||||
|
"muc_log_mam";
|
||||||
"muc_log_http";
|
"muc_log_http";
|
||||||
|
"muc_vcard";
|
||||||
}
|
}
|
||||||
|
|
||||||
muc_event_rate = 0.5
|
muc_event_rate = 0.5
|
||||||
muc_burst_factor = 10
|
muc_burst_factor = 10
|
||||||
|
|
||||||
muc_log_http_config = {
|
|
||||||
url_base = "logs";
|
|
||||||
theme = "metronome";
|
|
||||||
}
|
|
||||||
|
|
||||||
---Set up a PubSub server
|
---Set up a PubSub server
|
||||||
Component "pubsub.{{ main_domain }}" "pubsub"
|
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)
|
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
|
||||||
|
|
||||||
|
|
||||||
---Set up a VJUD service
|
---Set up a VJUD service
|
||||||
Component "vjud.{{ main_domain }}" "vjud"
|
Component "vjud.{{ main_domain }}" "vjud"
|
||||||
ud_disco_name = "{{ main_domain }} User Directory"
|
ud_disco_name = "{{ main_domain }} User Directory"
|
||||||
|
@ -190,4 +158,3 @@ Component "vjud.{{ main_domain }}" "vjud"
|
||||||
-- Settings under each VirtualHost entry apply *only* to that host.
|
-- Settings under each VirtualHost entry apply *only* to that host.
|
||||||
|
|
||||||
Include "conf.d/*.cfg.lua"
|
Include "conf.d/*.cfg.lua"
|
||||||
|
|
||||||
|
|
|
@ -73,6 +73,6 @@ server {
|
||||||
access_by_lua_file /usr/share/ssowat/access.lua;
|
access_by_lua_file /usr/share/ssowat/access.lua;
|
||||||
}
|
}
|
||||||
|
|
||||||
include conf.d/yunohost_admin.conf.inc;
|
include /etc/nginx/conf.d/yunohost_admin.conf.inc;
|
||||||
include conf.d/yunohost_api.conf.inc;
|
include /etc/nginx/conf.d/yunohost_api.conf.inc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ location /yunohost/api/ {
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
proxy_set_header Connection "upgrade";
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
|
||||||
# Custom 502 error page
|
# Custom 502 error page
|
||||||
error_page 502 /yunohost/api/error/502;
|
error_page 502 /yunohost/api/error/502;
|
||||||
|
|
|
@ -5,7 +5,7 @@ server {
|
||||||
|
|
||||||
access_by_lua_file /usr/share/ssowat/access.lua;
|
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 {
|
location /yunohost/admin {
|
||||||
return 301 https://$http_host$request_uri;
|
return 301 https://$http_host$request_uri;
|
||||||
|
@ -68,12 +68,21 @@ server {
|
||||||
add_header X-Permitted-Cross-Domain-Policies none;
|
add_header X-Permitted-Cross-Domain-Policies none;
|
||||||
add_header X-Frame-Options "SAMEORIGIN";
|
add_header 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;
|
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 /etc/nginx/conf.d/yunohost_admin.conf.inc;
|
||||||
include conf.d/yunohost_api.conf.inc;
|
include /etc/nginx/conf.d/yunohost_api.conf.inc;
|
||||||
|
|
||||||
access_log /var/log/nginx/{{ domain }}-access.log;
|
access_log /var/log/nginx/{{ domain }}-access.log;
|
||||||
error_log /var/log/nginx/{{ domain }}-error.log;
|
error_log /var/log/nginx/{{ domain }}-error.log;
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
# See ldap.conf(5) for details
|
# See ldap.conf(5) for details
|
||||||
# This file should be world readable but not world writable.
|
# This file should be world readable but not world writable.
|
||||||
|
|
||||||
#BASE dc=example,dc=com
|
BASE dc=yunohost,dc=org
|
||||||
#URI ldap://ldap.example.com ldap://ldap-master.example.com:666
|
URI ldap://localhost:389
|
||||||
|
|
||||||
#SIZELIMIT 12
|
#SIZELIMIT 12
|
||||||
#TIMELIMIT 15
|
#TIMELIMIT 15
|
||||||
|
|
|
@ -43,7 +43,7 @@ unique_subject = no # Set to 'no' to allow creation of
|
||||||
new_certs_dir = $dir/newcerts # default place for new certs.
|
new_certs_dir = $dir/newcerts # default place for new certs.
|
||||||
|
|
||||||
certificate = $dir/ca/cacert.pem # The CA certificate
|
certificate = $dir/ca/cacert.pem # The CA certificate
|
||||||
serial = $dir/serial # The current serial number
|
#serial = $dir/serial # The current serial number
|
||||||
#crlnumber = $dir/crlnumber # the current crl number
|
#crlnumber = $dir/crlnumber # the current crl number
|
||||||
# must be commented out to leave a V1 CRL
|
# must be commented out to leave a V1 CRL
|
||||||
crl = $dir/crl.pem # The current CRL
|
crl = $dir/crl.pem # The current CRL
|
||||||
|
|
36
debian/changelog
vendored
36
debian/changelog
vendored
|
@ -1,3 +1,39 @@
|
||||||
|
yunohost (3.2.2) stable; urgency=low
|
||||||
|
|
||||||
|
* [hotfix] mod_auth_ldap: reflect SASL API changes in latest Metronome (#546)
|
||||||
|
* [enh] Add the internal helper ynh_handle_getopts_args (#520)
|
||||||
|
|
||||||
|
Thanks to all contributors (Maranda, Maniack) ! <3
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Fri, 28 Sep 2018 23:04:00 +0000
|
||||||
|
|
||||||
|
yunohost (3.2.1) stable; urgency=low
|
||||||
|
|
||||||
|
* Don't send an email if no certificate needs to be renewed (#540)
|
||||||
|
* Fix an issue with home backups (#541)
|
||||||
|
* Fix an issue with installs on OVH VPS
|
||||||
|
* Tell the user about post-install available in browser in bootprompt (#544)
|
||||||
|
* Improve Arabic translation
|
||||||
|
|
||||||
|
Thanks to all contributors (BoF, ljf, Aleks) ! <3
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Mon, 17 Sep 2018 18:06:00 +0000
|
||||||
|
|
||||||
|
yunohost (3.2.0) stable; urgency=low
|
||||||
|
|
||||||
|
* Add many print and exec helpers (#523)
|
||||||
|
* Add su directive as option for logrotate helper (#511)
|
||||||
|
* Add equivs, fake-hwclock and jq as base dependencies (#515, #514, #532)
|
||||||
|
* Allow to add a service description on "yunohost service add" (#529)
|
||||||
|
* Add option '--need-lock' to 'yunohost service add' (#530)
|
||||||
|
* Don't backup user home with .nobackup file (#536)
|
||||||
|
* Add a script to automatically generate helpers documentation (#538)
|
||||||
|
* [i18n] Improve Arabic translation
|
||||||
|
|
||||||
|
Thanks to all contributors (Bram, Maniack, irina11y, Josue, BoF, ljf, Aleks) ! <3
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Tue, 11 Sep 2018 16:30:00 +0000
|
||||||
|
|
||||||
yunohost (3.2.0~testing1) testing; urgency=low
|
yunohost (3.2.0~testing1) testing; urgency=low
|
||||||
|
|
||||||
* Add logging system of every unit operation (#165)
|
* Add logging system of every unit operation (#165)
|
||||||
|
|
4
debian/control
vendored
4
debian/control
vendored
|
@ -14,7 +14,7 @@ Depends: ${python:Depends}, ${misc:Depends}
|
||||||
, python-psutil, python-requests, python-dnspython, python-openssl
|
, python-psutil, python-requests, python-dnspython, python-openssl
|
||||||
, python-apt, python-miniupnpc, python-dbus, python-jinja2
|
, python-apt, python-miniupnpc, python-dbus, python-jinja2
|
||||||
, glances
|
, glances
|
||||||
, dnsutils, bind9utils, unzip, git, curl, cron, wget
|
, dnsutils, bind9utils, unzip, git, curl, cron, wget, jq
|
||||||
, ca-certificates, netcat-openbsd, iproute
|
, ca-certificates, netcat-openbsd, iproute
|
||||||
, mariadb-server, php-mysql | php-mysqlnd
|
, mariadb-server, php-mysql | php-mysqlnd
|
||||||
, slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd
|
, slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd
|
||||||
|
@ -25,7 +25,7 @@ Depends: ${python:Depends}, ${misc:Depends}
|
||||||
, dnsmasq, openssl, avahi-daemon, libnss-mdns, resolvconf, libnss-myhostname
|
, dnsmasq, openssl, avahi-daemon, libnss-mdns, resolvconf, libnss-myhostname
|
||||||
, metronome
|
, metronome
|
||||||
, rspamd (>= 1.6.0), redis-server, opendkim-tools
|
, rspamd (>= 1.6.0), redis-server, opendkim-tools
|
||||||
, haveged
|
, haveged, fake-hwclock
|
||||||
, equivs
|
, equivs
|
||||||
Recommends: yunohost-admin
|
Recommends: yunohost-admin
|
||||||
, openssh-server, ntp, inetutils-ping | iputils-ping
|
, openssh-server, ntp, inetutils-ping | iputils-ping
|
||||||
|
|
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/actionsmap/* /usr/share/moulinette/actionsmap/
|
||||||
data/hooks/* /usr/share/yunohost/hooks/
|
data/hooks/* /usr/share/yunohost/hooks/
|
||||||
data/other/yunoprompt.service /etc/systemd/system/
|
data/other/yunoprompt.service /etc/systemd/system/
|
||||||
|
data/other/password/* /usr/share/yunohost/other/password/
|
||||||
data/other/* /usr/share/yunohost/yunohost-config/moulinette/
|
data/other/* /usr/share/yunohost/yunohost-config/moulinette/
|
||||||
data/templates/* /usr/share/yunohost/templates/
|
data/templates/* /usr/share/yunohost/templates/
|
||||||
data/helpers /usr/share/yunohost/
|
data/helpers /usr/share/yunohost/
|
||||||
|
|
171
doc/generate_helper_doc.py
Normal file
171
doc/generate_helper_doc.py
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
#!/usr/env/python2.7
|
||||||
|
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
def render(data):
|
||||||
|
|
||||||
|
from jinja2 import Template
|
||||||
|
from ansi2html import Ansi2HTMLConverter
|
||||||
|
from ansi2html.style import get_styles
|
||||||
|
|
||||||
|
conv = Ansi2HTMLConverter()
|
||||||
|
shell_css = "\n".join(map(str, get_styles(conv.dark_bg, conv.scheme)))
|
||||||
|
|
||||||
|
def shell_to_html(shell):
|
||||||
|
return conv.convert(shell, False)
|
||||||
|
|
||||||
|
template = open("helper_doc_template.html", "r").read()
|
||||||
|
t = Template(template)
|
||||||
|
t.globals['now'] = datetime.datetime.utcnow
|
||||||
|
result = t.render(data=data, convert=shell_to_html, shell_css=shell_css)
|
||||||
|
open("helpers.html", "w").write(result)
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
class Parser():
|
||||||
|
|
||||||
|
def __init__(self, filename):
|
||||||
|
|
||||||
|
self.file = open(filename, "r").readlines()
|
||||||
|
self.blocks = None
|
||||||
|
|
||||||
|
def parse_blocks(self):
|
||||||
|
|
||||||
|
self.blocks = []
|
||||||
|
|
||||||
|
current_reading = "void"
|
||||||
|
current_block = { "name": None,
|
||||||
|
"line": -1,
|
||||||
|
"comments": [],
|
||||||
|
"code": [] }
|
||||||
|
|
||||||
|
for i, line in enumerate(self.file):
|
||||||
|
|
||||||
|
line = line.rstrip().replace("\t", " ")
|
||||||
|
|
||||||
|
if current_reading == "void":
|
||||||
|
if is_global_comment(line):
|
||||||
|
# We start a new comment bloc
|
||||||
|
current_reading = "comments"
|
||||||
|
assert line.startswith("# ") or line == "#", malformed_error(i)
|
||||||
|
current_block["comments"].append(line[2:])
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
#assert line == "", malformed_error(i)
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif current_reading == "comments":
|
||||||
|
if is_global_comment(line):
|
||||||
|
# We're still in a comment bloc
|
||||||
|
assert line.startswith("# ") or line == "#", malformed_error(i)
|
||||||
|
current_block["comments"].append(line[2:])
|
||||||
|
else:
|
||||||
|
# We're getting out of a comment bloc, we should find
|
||||||
|
# the name of the function
|
||||||
|
assert len(line.split()) >= 1
|
||||||
|
current_block["line"] = i
|
||||||
|
current_block["name"] = line.split()[0].strip("(){")
|
||||||
|
# Then we expect to read the function
|
||||||
|
current_reading = "code"
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif current_reading == "code":
|
||||||
|
|
||||||
|
if line == "}":
|
||||||
|
# We're getting out of the function
|
||||||
|
current_reading = "void"
|
||||||
|
|
||||||
|
# Then we keep this bloc and start a new one
|
||||||
|
# (we ignore helpers containing [internal] ...)
|
||||||
|
if not "[internal]" in current_block["comments"]:
|
||||||
|
self.blocks.append(current_block)
|
||||||
|
current_block = { "name": None,
|
||||||
|
"line": -1,
|
||||||
|
"comments": [],
|
||||||
|
"code": [] }
|
||||||
|
else:
|
||||||
|
current_block["code"].append(line)
|
||||||
|
pass
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
def parse_block(self, b):
|
||||||
|
|
||||||
|
b["brief"] = ""
|
||||||
|
b["details"] = ""
|
||||||
|
b["usage"] = ""
|
||||||
|
b["args"] = []
|
||||||
|
b["ret"] = ""
|
||||||
|
b["example"] = ""
|
||||||
|
|
||||||
|
subblocks = '\n'.join(b["comments"]).split("\n\n")
|
||||||
|
|
||||||
|
for i, subblock in enumerate(subblocks):
|
||||||
|
subblock = subblock.strip()
|
||||||
|
|
||||||
|
if i == 0:
|
||||||
|
b["brief"] = subblock
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif subblock.startswith("example"):
|
||||||
|
b["example"] = " ".join(subblock.split()[1:])
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif subblock.startswith("usage"):
|
||||||
|
for line in subblock.split("\n"):
|
||||||
|
|
||||||
|
if line.startswith("| arg"):
|
||||||
|
argname = line.split()[2]
|
||||||
|
argdescr = " ".join(line.split()[4:])
|
||||||
|
b["args"].append((argname, argdescr))
|
||||||
|
elif line.startswith("| ret"):
|
||||||
|
b["ret"] = " ".join(line.split()[2:])
|
||||||
|
else:
|
||||||
|
if line.startswith("usage"):
|
||||||
|
line = " ".join(line.split()[1:])
|
||||||
|
b["usage"] += line + "\n"
|
||||||
|
continue
|
||||||
|
|
||||||
|
elif subblock.startswith("| arg"):
|
||||||
|
for line in subblock.split("\n"):
|
||||||
|
if line.startswith("| arg"):
|
||||||
|
argname = line.split()[2]
|
||||||
|
argdescr = line.split()[4:]
|
||||||
|
b["args"].append((argname, argdescr))
|
||||||
|
continue
|
||||||
|
|
||||||
|
else:
|
||||||
|
b["details"] += subblock + "\n\n"
|
||||||
|
|
||||||
|
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():
|
||||||
|
|
||||||
|
helper_files = sorted(glob.glob("../data/helpers.d/*"))
|
||||||
|
helpers = []
|
||||||
|
|
||||||
|
for helper_file in helper_files:
|
||||||
|
category_name = os.path.basename(helper_file)
|
||||||
|
print "Parsing %s ..." % category_name
|
||||||
|
p = Parser(helper_file)
|
||||||
|
p.parse_blocks()
|
||||||
|
for b in p.blocks:
|
||||||
|
p.parse_block(b)
|
||||||
|
|
||||||
|
helpers.append((category_name, p.blocks))
|
||||||
|
|
||||||
|
render(helpers)
|
||||||
|
|
||||||
|
main()
|
||||||
|
|
93
doc/helper_doc_template.html
Normal file
93
doc/helper_doc_template.html
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<!-- NO_MARKDOWN_PARSING -->
|
||||||
|
|
||||||
|
<h1>App helpers</h1>
|
||||||
|
|
||||||
|
{% for category, helpers in data %}
|
||||||
|
|
||||||
|
<h3 style="text-transform: uppercase; font-weight: bold">{{ category }}</h3>
|
||||||
|
|
||||||
|
{% for h in helpers %}
|
||||||
|
|
||||||
|
<div class="helper-card">
|
||||||
|
<div class="helper-card-body">
|
||||||
|
<div data-toggle="collapse" href="#collapse-{{ h.name }}" style="cursor:pointer">
|
||||||
|
<h5 class="helper-card-title"><tt>{{ h.name }}</tt></h5>
|
||||||
|
<h6 class="helper-card-subtitle text-muted">{{ h.brief }}</h6>
|
||||||
|
</div>
|
||||||
|
<div id="collapse-{{ h.name }}" class="collapse" role="tabpanel">
|
||||||
|
<hr style="margin-top:25px; margin-bottom:25px;">
|
||||||
|
<p>
|
||||||
|
{% if not '\n' in h.usage %}
|
||||||
|
<strong>Usage</strong>: <code class="helper-code">{{ h.usage }}</code>
|
||||||
|
{% else %}
|
||||||
|
<strong>Usage</strong>: <code class="helper-code helper-usage">{{ h.usage }}</code>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
{% if h.args %}
|
||||||
|
<p>
|
||||||
|
<strong>Arguments</strong>:
|
||||||
|
<ul>
|
||||||
|
{% for name, descr in h.args %}
|
||||||
|
<li><code>{{ name }}</code> : {{ descr }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if h.ret %}
|
||||||
|
<p>
|
||||||
|
<strong>Returns</strong>: {{ h.ret }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if h.example %}
|
||||||
|
<p>
|
||||||
|
<strong>Example</strong>: <code class="helper-code">{{ h.example }}</code>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% if h.details %}
|
||||||
|
<p>
|
||||||
|
<strong>Details</strong>:
|
||||||
|
<p>
|
||||||
|
{{ h.details.replace('\n', '</br>') }}
|
||||||
|
</p>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<p>
|
||||||
|
<a href="https://github.com/YunoHost/yunohost/blob/stretch-unstable/data/helpers.d/{{ category }}#L{{ h.line + 1 }}">Dude, show me the code !</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/*=================================================
|
||||||
|
Helper card
|
||||||
|
=================================================*/
|
||||||
|
.helper-card {
|
||||||
|
width:100%;
|
||||||
|
min-height: 1px;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-left: 10px;
|
||||||
|
border: 1px solid rgba(0,0,0,.125);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
word-wrap: break-word;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.helper-card-body {
|
||||||
|
padding: 1.25rem;
|
||||||
|
padding-top: 0.8rem;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
.helper-code {
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
/*===============================================*/
|
||||||
|
|
||||||
|
</style>
|
|
@ -56,8 +56,9 @@ function new_default_provider(host)
|
||||||
return nil, "Account creation/modification not available with LDAP.";
|
return nil, "Account creation/modification not available with LDAP.";
|
||||||
end
|
end
|
||||||
|
|
||||||
function provider.get_sasl_handler()
|
function provider.get_sasl_handler(session)
|
||||||
local testpass_authentication_profile = {
|
local testpass_authentication_profile = {
|
||||||
|
session = session,
|
||||||
plain_test = function(sasl, username, password, realm)
|
plain_test = function(sasl, username, password, realm)
|
||||||
return provider.test_password(username, password), true;
|
return provider.test_password(username, password), true;
|
||||||
end,
|
end,
|
||||||
|
|
|
@ -296,8 +296,8 @@
|
||||||
"restore_system_part_failed": "Unable to restore the '{part:s}' system part",
|
"restore_system_part_failed": "Unable to restore the '{part:s}' system part",
|
||||||
"server_shutdown": "سوف ينطفئ الخادوم",
|
"server_shutdown": "سوف ينطفئ الخادوم",
|
||||||
"server_shutdown_confirm": "سوف ينطفئ الخادوم حالا. متأكد ؟ [{answers:s}]",
|
"server_shutdown_confirm": "سوف ينطفئ الخادوم حالا. متأكد ؟ [{answers:s}]",
|
||||||
"server_reboot": "The server will reboot",
|
"server_reboot": "سيعاد تشغيل الخادوم",
|
||||||
"server_reboot_confirm": "The server will reboot immediatly, are you sure? [{answers:s}]",
|
"server_reboot_confirm": "سيعاد تشغيل الخادوم في الحين. هل أنت متأكد ؟ [{answers:s}]",
|
||||||
"service_add_failed": "تعذرت إضافة خدمة '{service:s}'",
|
"service_add_failed": "تعذرت إضافة خدمة '{service:s}'",
|
||||||
"service_added": "The service '{service:s}' has been added",
|
"service_added": "The service '{service:s}' has been added",
|
||||||
"service_already_started": "Service '{service:s}' has already been started",
|
"service_already_started": "Service '{service:s}' has already been started",
|
||||||
|
|
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}"
|
||||||
|
}
|
|
@ -130,7 +130,6 @@
|
||||||
"certmanager_hit_rate_limit": "Too many certificates already issued for exact set of domains {domain:s} recently. Please try again later. See https://letsencrypt.org/docs/rate-limits/ for more details",
|
"certmanager_hit_rate_limit": "Too many certificates already issued for exact set of domains {domain:s} recently. Please try again later. See https://letsencrypt.org/docs/rate-limits/ for more details",
|
||||||
"certmanager_http_check_timeout": "Timed out when server tried to contact itself through HTTP using public IP address (domain {domain:s} with ip {ip:s}). You may be experiencing hairpinning issue or the firewall/router ahead of your server is misconfigured.",
|
"certmanager_http_check_timeout": "Timed out when server tried to contact itself through HTTP using public IP address (domain {domain:s} with ip {ip:s}). You may be experiencing hairpinning issue or the firewall/router ahead of your server is misconfigured.",
|
||||||
"certmanager_no_cert_file": "Unable to read certificate file for domain {domain:s} (file: {file:s})",
|
"certmanager_no_cert_file": "Unable to read certificate file for domain {domain:s} (file: {file:s})",
|
||||||
"certmanager_old_letsencrypt_app_detected": "\nYunohost detected that the 'letsencrypt' app is installed, which conflits with the new built-in certificate management features in Yunohost. If you wish to use the new built-in features, please run the following commands to migrate your installation:\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B.: this will attempt to re-install certificates for all domains with a Let's Encrypt certificate or self-signed certificate",
|
|
||||||
"certmanager_self_ca_conf_file_not_found": "Configuration file not found for self-signing authority (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})",
|
"certmanager_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority (file: {file:s})",
|
||||||
"custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}",
|
"custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}",
|
||||||
|
@ -197,6 +196,8 @@
|
||||||
"global_settings_setting_example_string": "Example string option",
|
"global_settings_setting_example_string": "Example string option",
|
||||||
"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_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_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.",
|
"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_failed": "Script execution failed: {path:s}",
|
||||||
"hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}",
|
"hook_exec_not_terminated": "Script execution hasn\u2019t terminated: {path:s}",
|
||||||
"hook_list_by_invalid": "Invalid property to list hook by",
|
"hook_list_by_invalid": "Invalid property to list hook by",
|
||||||
|
@ -256,6 +257,7 @@
|
||||||
"mail_domain_unknown": "Unknown mail address domain '{domain:s}'",
|
"mail_domain_unknown": "Unknown mail address domain '{domain:s}'",
|
||||||
"mail_forward_remove_failed": "Unable to remove mail forward '{mail: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",
|
"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_change_failed": "Unable to change the main domain",
|
||||||
"maindomain_changed": "The main domain has been changed",
|
"maindomain_changed": "The main domain has been changed",
|
||||||
"migrate_tsig_end": "Migration to hmac-sha512 finished",
|
"migrate_tsig_end": "Migration to hmac-sha512 finished",
|
||||||
|
@ -332,6 +334,11 @@
|
||||||
"packages_no_upgrade": "There is no package to upgrade",
|
"packages_no_upgrade": "There is no package to upgrade",
|
||||||
"packages_upgrade_critical_later": "Critical packages ({packages:s}) will be upgraded later",
|
"packages_upgrade_critical_later": "Critical packages ({packages:s}) will be upgraded later",
|
||||||
"packages_upgrade_failed": "Unable to upgrade all of the packages",
|
"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}",
|
"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_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)",
|
"pattern_domain": "Must be a valid domain name (e.g. my-domain.org)",
|
||||||
|
|
|
@ -346,7 +346,8 @@ def app_info(app, show_status=False, raw=False):
|
||||||
ret['settings'] = _get_app_settings(app)
|
ret['settings'] = _get_app_settings(app)
|
||||||
|
|
||||||
# Determine upgradability
|
# 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:
|
if 'lastUpdate' not in ret or 'git' not in ret:
|
||||||
upgradable = "url_required"
|
upgradable = "url_required"
|
||||||
|
@ -513,7 +514,7 @@ def app_change_url(operation_logger, auth, app, domain, path):
|
||||||
os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts", "change_url")))
|
os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts", "change_url")))
|
||||||
|
|
||||||
if hook_exec(os.path.join(APP_TMP_FOLDER, 'scripts/change_url'),
|
if hook_exec(os.path.join(APP_TMP_FOLDER, 'scripts/change_url'),
|
||||||
args=args_list, env=env_dict, user="root") != 0:
|
args=args_list, env=env_dict) != 0:
|
||||||
msg = "Failed to change '%s' url." % app
|
msg = "Failed to change '%s' url." % app
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
operation_logger.error(msg)
|
operation_logger.error(msg)
|
||||||
|
@ -640,7 +641,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
||||||
# Execute App upgrade script
|
# Execute App upgrade script
|
||||||
os.system('chown -hR admin: %s' % INSTALL_TMP)
|
os.system('chown -hR admin: %s' % INSTALL_TMP)
|
||||||
if hook_exec(extracted_app_folder + '/scripts/upgrade',
|
if hook_exec(extracted_app_folder + '/scripts/upgrade',
|
||||||
args=args_list, env=env_dict, user="root") != 0:
|
args=args_list, env=env_dict) != 0:
|
||||||
msg = m18n.n('app_upgrade_failed', app=app_instance_name)
|
msg = m18n.n('app_upgrade_failed', app=app_instance_name)
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
operation_logger.error(msg)
|
operation_logger.error(msg)
|
||||||
|
@ -800,7 +801,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
|
||||||
try:
|
try:
|
||||||
install_retcode = hook_exec(
|
install_retcode = hook_exec(
|
||||||
os.path.join(extracted_app_folder, 'scripts/install'),
|
os.path.join(extracted_app_folder, 'scripts/install'),
|
||||||
args=args_list, env=env_dict, user="root"
|
args=args_list, env=env_dict
|
||||||
)
|
)
|
||||||
except (KeyboardInterrupt, EOFError):
|
except (KeyboardInterrupt, EOFError):
|
||||||
install_retcode = -1
|
install_retcode = -1
|
||||||
|
@ -824,7 +825,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
|
||||||
|
|
||||||
remove_retcode = hook_exec(
|
remove_retcode = hook_exec(
|
||||||
os.path.join(extracted_app_folder, 'scripts/remove'),
|
os.path.join(extracted_app_folder, 'scripts/remove'),
|
||||||
args=[app_instance_name], env=env_dict_remove, user="root"
|
args=[app_instance_name], env=env_dict_remove
|
||||||
)
|
)
|
||||||
if remove_retcode != 0:
|
if remove_retcode != 0:
|
||||||
msg = m18n.n('app_not_properly_removed',
|
msg = m18n.n('app_not_properly_removed',
|
||||||
|
@ -912,7 +913,7 @@ def app_remove(operation_logger, auth, app):
|
||||||
operation_logger.flush()
|
operation_logger.flush()
|
||||||
|
|
||||||
if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list,
|
if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list,
|
||||||
env=env_dict, user="root") == 0:
|
env=env_dict) == 0:
|
||||||
logger.success(m18n.n('app_removed', app=app))
|
logger.success(m18n.n('app_removed', app=app))
|
||||||
|
|
||||||
hook_callback('post_app_remove', args=args_list, env=env_dict)
|
hook_callback('post_app_remove', args=args_list, env=env_dict)
|
||||||
|
@ -1461,33 +1462,33 @@ def app_change_label(auth, app, new_label):
|
||||||
# actions todo list:
|
# actions todo list:
|
||||||
# * docstring
|
# * docstring
|
||||||
|
|
||||||
def app_action_list(app_id):
|
def app_action_list(app):
|
||||||
logger.warning(m18n.n('experimental_feature'))
|
logger.warning(m18n.n('experimental_feature'))
|
||||||
|
|
||||||
# this will take care of checking if the app is installed
|
# this will take care of checking if the app is installed
|
||||||
app_info_dict = app_info(app_id)
|
app_info_dict = app_info(app)
|
||||||
|
|
||||||
actions = os.path.join(APPS_SETTING_PATH, app_id, 'actions.json')
|
actions = os.path.join(APPS_SETTING_PATH, app, 'actions.json')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"app_id": app_id,
|
"app": app,
|
||||||
"app_name": app_info_dict["name"],
|
"app_name": app_info_dict["name"],
|
||||||
"actions": read_json(actions) if os.path.exists(actions) else [],
|
"actions": read_json(actions) if os.path.exists(actions) else [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def app_action_run(app_id, action, args=None):
|
def app_action_run(app, action, args=None):
|
||||||
logger.warning(m18n.n('experimental_feature'))
|
logger.warning(m18n.n('experimental_feature'))
|
||||||
|
|
||||||
from yunohost.hook import hook_exec
|
from yunohost.hook import hook_exec
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
# will raise if action doesn't exist
|
# will raise if action doesn't exist
|
||||||
actions = app_action_list(app_id)["actions"]
|
actions = app_action_list(app)["actions"]
|
||||||
actions = {x["id"]: x for x in actions}
|
actions = {x["id"]: x for x in actions}
|
||||||
|
|
||||||
if action not in actions:
|
if action not in actions:
|
||||||
raise MoulinetteError(errno.EINVAL, "action '%s' not available for app '%s', available actions are: %s" % (action, app_id, ", ".join(actions.keys())))
|
raise MoulinetteError(errno.EINVAL, "action '%s' not available for app '%s', available actions are: %s" % (action, app, ", ".join(actions.keys())))
|
||||||
|
|
||||||
action_declaration = actions[action]
|
action_declaration = actions[action]
|
||||||
|
|
||||||
|
@ -1496,8 +1497,12 @@ def app_action_run(app_id, action, args=None):
|
||||||
args_odict = _parse_args_for_action(actions[action], args=args_dict)
|
args_odict = _parse_args_for_action(actions[action], args=args_dict)
|
||||||
args_list = args_odict.values()
|
args_list = args_odict.values()
|
||||||
|
|
||||||
|
app_id, app_instance_nb = _parse_app_instance_name(app)
|
||||||
|
|
||||||
env_dict = _make_environment_dict(args_odict, prefix="ACTION_")
|
env_dict = _make_environment_dict(args_odict, prefix="ACTION_")
|
||||||
env_dict["YNH_APP_ID"] = app_id
|
env_dict["YNH_APP_ID"] = app_id
|
||||||
|
env_dict["YNH_APP_INSTANCE_NAME"] = app
|
||||||
|
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
|
||||||
env_dict["YNH_ACTION"] = action
|
env_dict["YNH_ACTION"] = action
|
||||||
|
|
||||||
_, path = tempfile.mkstemp()
|
_, path = tempfile.mkstemp()
|
||||||
|
@ -1508,9 +1513,9 @@ def app_action_run(app_id, action, args=None):
|
||||||
os.chmod(path, 700)
|
os.chmod(path, 700)
|
||||||
|
|
||||||
if action_declaration.get("cwd"):
|
if action_declaration.get("cwd"):
|
||||||
cwd = action_declaration["cwd"].replace("$app_id", app_id)
|
cwd = action_declaration["cwd"].replace("$app", app_id)
|
||||||
else:
|
else:
|
||||||
cwd = "/etc/yunohost/apps/" + app_id
|
cwd = "/etc/yunohost/apps/" + app
|
||||||
|
|
||||||
retcode = hook_exec(
|
retcode = hook_exec(
|
||||||
path,
|
path,
|
||||||
|
@ -1521,7 +1526,7 @@ def app_action_run(app_id, action, args=None):
|
||||||
)
|
)
|
||||||
|
|
||||||
if retcode not in action_declaration.get("accepted_return_codes", [0]):
|
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_id, retcode))
|
raise MoulinetteError(retcode, "Error while executing action '%s' of app '%s': return code %s" % (action, app, retcode))
|
||||||
|
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
|
||||||
|
@ -1531,25 +1536,34 @@ def app_action_run(app_id, action, args=None):
|
||||||
# Config panel todo list:
|
# Config panel todo list:
|
||||||
# * docstrings
|
# * docstrings
|
||||||
# * merge translations on the json once the workflow is in place
|
# * merge translations on the json once the workflow is in place
|
||||||
def app_config_show_panel(app_id):
|
def app_config_show_panel(app):
|
||||||
logger.warning(m18n.n('experimental_feature'))
|
logger.warning(m18n.n('experimental_feature'))
|
||||||
|
|
||||||
from yunohost.hook import hook_exec
|
from yunohost.hook import hook_exec
|
||||||
|
|
||||||
# this will take care of checking if the app is installed
|
# this will take care of checking if the app is installed
|
||||||
app_info_dict = app_info(app_id)
|
app_info_dict = app_info(app)
|
||||||
|
|
||||||
config_panel = os.path.join(APPS_SETTING_PATH, app_id, 'config_panel.json')
|
config_panel = os.path.join(APPS_SETTING_PATH, app, 'config_panel.json')
|
||||||
config_script = os.path.join(APPS_SETTING_PATH, app_id, 'scripts', 'config')
|
config_script = os.path.join(APPS_SETTING_PATH, app, 'scripts', 'config')
|
||||||
|
|
||||||
|
app_id, app_instance_nb = _parse_app_instance_name(app)
|
||||||
|
|
||||||
if not os.path.exists(config_panel) or not os.path.exists(config_script):
|
if not os.path.exists(config_panel) or not os.path.exists(config_script):
|
||||||
return {
|
return {
|
||||||
|
"app_id": app_id,
|
||||||
|
"app": app,
|
||||||
|
"app_name": app_info_dict["name"],
|
||||||
"config_panel": [],
|
"config_panel": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
config_panel = read_json(config_panel)
|
config_panel = read_json(config_panel)
|
||||||
|
|
||||||
env = {"YNH_APP_ID": app_id}
|
env = {
|
||||||
|
"YNH_APP_ID": app_id,
|
||||||
|
"YNH_APP_INSTANCE_NAME": app,
|
||||||
|
"YNH_APP_INSTANCE_NUMBER": str(app_instance_nb),
|
||||||
|
}
|
||||||
parsed_values = {}
|
parsed_values = {}
|
||||||
|
|
||||||
# I need to parse stdout to communicate between scripts because I can't
|
# I need to parse stdout to communicate between scripts because I can't
|
||||||
|
@ -1573,7 +1587,6 @@ def app_config_show_panel(app_id):
|
||||||
return_code = hook_exec(config_script,
|
return_code = hook_exec(config_script,
|
||||||
args=["show"],
|
args=["show"],
|
||||||
env=env,
|
env=env,
|
||||||
user="root",
|
|
||||||
stdout_callback=parse_stdout,
|
stdout_callback=parse_stdout,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1607,23 +1620,24 @@ def app_config_show_panel(app_id):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"app_id": app_id,
|
"app_id": app_id,
|
||||||
|
"app": app,
|
||||||
"app_name": app_info_dict["name"],
|
"app_name": app_info_dict["name"],
|
||||||
"config_panel": config_panel,
|
"config_panel": config_panel,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def app_config_apply(app_id, args):
|
def app_config_apply(app, args):
|
||||||
logger.warning(m18n.n('experimental_feature'))
|
logger.warning(m18n.n('experimental_feature'))
|
||||||
|
|
||||||
from yunohost.hook import hook_exec
|
from yunohost.hook import hook_exec
|
||||||
|
|
||||||
installed = _is_installed(app_id)
|
installed = _is_installed(app)
|
||||||
if not installed:
|
if not installed:
|
||||||
raise MoulinetteError(errno.ENOPKG,
|
raise MoulinetteError(errno.ENOPKG,
|
||||||
m18n.n('app_not_installed', app=app_id))
|
m18n.n('app_not_installed', app=app))
|
||||||
|
|
||||||
config_panel = os.path.join(APPS_SETTING_PATH, app_id, 'config_panel.json')
|
config_panel = os.path.join(APPS_SETTING_PATH, app, 'config_panel.json')
|
||||||
config_script = os.path.join(APPS_SETTING_PATH, app_id, 'scripts', 'config')
|
config_script = os.path.join(APPS_SETTING_PATH, app, 'scripts', 'config')
|
||||||
|
|
||||||
if not os.path.exists(config_panel) or not os.path.exists(config_script):
|
if not os.path.exists(config_panel) or not os.path.exists(config_script):
|
||||||
# XXX real exception
|
# XXX real exception
|
||||||
|
@ -1631,7 +1645,12 @@ def app_config_apply(app_id, args):
|
||||||
|
|
||||||
config_panel = read_json(config_panel)
|
config_panel = read_json(config_panel)
|
||||||
|
|
||||||
env = {"YNH_APP_ID": app_id}
|
app_id, app_instance_nb = _parse_app_instance_name(app)
|
||||||
|
env = {
|
||||||
|
"YNH_APP_ID": app_id,
|
||||||
|
"YNH_APP_INSTANCE_NAME": app,
|
||||||
|
"YNH_APP_INSTANCE_NUMBER": str(app_instance_nb),
|
||||||
|
}
|
||||||
args = dict(urlparse.parse_qsl(args, keep_blank_values=True)) if args else {}
|
args = dict(urlparse.parse_qsl(args, keep_blank_values=True)) if args else {}
|
||||||
|
|
||||||
for tab in config_panel.get("panel", []):
|
for tab in config_panel.get("panel", []):
|
||||||
|
@ -1656,7 +1675,6 @@ def app_config_apply(app_id, args):
|
||||||
return_code = hook_exec(config_script,
|
return_code = hook_exec(config_script,
|
||||||
args=["apply"],
|
args=["apply"],
|
||||||
env=env,
|
env=env,
|
||||||
user="root",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if return_code != 0:
|
if return_code != 0:
|
||||||
|
@ -2189,11 +2207,15 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None):
|
||||||
for domain in domain_list(auth)['domains']:
|
for domain in domain_list(auth)['domains']:
|
||||||
msignals.display("- {}".format(domain))
|
msignals.display("- {}".format(domain))
|
||||||
|
|
||||||
if arg_type == 'user':
|
elif arg_type == 'user':
|
||||||
msignals.display(m18n.n('users_available'))
|
msignals.display(m18n.n('users_available'))
|
||||||
for user in user_list(auth)['users'].keys():
|
for user in user_list(auth)['users'].keys():
|
||||||
msignals.display("- {}".format(user))
|
msignals.display("- {}".format(user))
|
||||||
|
|
||||||
|
elif arg_type == 'password':
|
||||||
|
msignals.display(m18n.n('good_practices_about_user_password'))
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
input_string = msignals.prompt(ask_string, is_password)
|
input_string = msignals.prompt(ask_string, is_password)
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
|
@ -2251,6 +2273,9 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None):
|
||||||
raise MoulinetteError(errno.EINVAL,
|
raise MoulinetteError(errno.EINVAL,
|
||||||
m18n.n('app_argument_choice_invalid',
|
m18n.n('app_argument_choice_invalid',
|
||||||
name=arg_name, choices='yes, no, y, n, 1, 0'))
|
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
|
args_dict[arg_name] = arg_value
|
||||||
|
|
||||||
# END loop over action_args...
|
# END loop over action_args...
|
||||||
|
|
|
@ -680,7 +680,7 @@ class BackupManager():
|
||||||
subprocess.call(['install', '-Dm555', app_script, tmp_script])
|
subprocess.call(['install', '-Dm555', app_script, tmp_script])
|
||||||
|
|
||||||
hook_exec(tmp_script, args=[tmp_app_bkp_dir, app],
|
hook_exec(tmp_script, args=[tmp_app_bkp_dir, app],
|
||||||
raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict, user="root")
|
raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict)
|
||||||
|
|
||||||
self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"])
|
self._import_to_list_to_backup(env_dict["YNH_BACKUP_CSV"])
|
||||||
except:
|
except:
|
||||||
|
@ -1310,8 +1310,7 @@ class RestoreManager():
|
||||||
args=[app_backup_in_archive, app_instance_name],
|
args=[app_backup_in_archive, app_instance_name],
|
||||||
chdir=app_backup_in_archive,
|
chdir=app_backup_in_archive,
|
||||||
raise_on_error=True,
|
raise_on_error=True,
|
||||||
env=env_dict,
|
env=env_dict)
|
||||||
user="root")
|
|
||||||
except:
|
except:
|
||||||
msg = m18n.n('restore_app_failed',app=app_instance_name)
|
msg = m18n.n('restore_app_failed',app=app_instance_name)
|
||||||
logger.exception(msg)
|
logger.exception(msg)
|
||||||
|
@ -1336,7 +1335,7 @@ class RestoreManager():
|
||||||
# Execute remove script
|
# Execute remove script
|
||||||
# TODO: call app_remove instead
|
# TODO: call app_remove instead
|
||||||
if hook_exec(remove_script, args=[app_instance_name],
|
if hook_exec(remove_script, args=[app_instance_name],
|
||||||
env=env_dict_remove, user="root") != 0:
|
env=env_dict_remove) != 0:
|
||||||
msg = m18n.n('app_not_properly_removed', app=app_instance_name)
|
msg = m18n.n('app_not_properly_removed', app=app_instance_name)
|
||||||
logger.warning(msg)
|
logger.warning(msg)
|
||||||
operation_logger.error(msg)
|
operation_logger.error(msg)
|
||||||
|
@ -2188,7 +2187,7 @@ def backup_list(with_info=False, human_readable=False):
|
||||||
except ValueError:
|
except ValueError:
|
||||||
continue
|
continue
|
||||||
result.append(name)
|
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:
|
if result and with_info:
|
||||||
d = OrderedDict()
|
d = OrderedDict()
|
||||||
|
|
|
@ -97,11 +97,6 @@ def certificate_status(auth, domain_list, full=False):
|
||||||
|
|
||||||
import yunohost.domain
|
import yunohost.domain
|
||||||
|
|
||||||
# Check if old letsencrypt_ynh is installed
|
|
||||||
# TODO / FIXME - Remove this in the future once the letsencrypt app is
|
|
||||||
# not used anymore
|
|
||||||
_check_old_letsencrypt_app()
|
|
||||||
|
|
||||||
# If no domains given, consider all yunohost domains
|
# If no domains given, consider all yunohost domains
|
||||||
if domain_list == []:
|
if domain_list == []:
|
||||||
domain_list = yunohost.domain.domain_list(auth)['domains']
|
domain_list = yunohost.domain.domain_list(auth)['domains']
|
||||||
|
@ -144,11 +139,6 @@ def certificate_install(auth, domain_list, force=False, no_checks=False, self_si
|
||||||
self-signed -- Instal self-signed certificates instead of Let's Encrypt
|
self-signed -- Instal self-signed certificates instead of Let's Encrypt
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Check if old letsencrypt_ynh is installed
|
|
||||||
# TODO / FIXME - Remove this in the future once the letsencrypt app is
|
|
||||||
# not used anymore
|
|
||||||
_check_old_letsencrypt_app()
|
|
||||||
|
|
||||||
if self_signed:
|
if self_signed:
|
||||||
_certificate_install_selfsigned(domain_list, force)
|
_certificate_install_selfsigned(domain_list, force)
|
||||||
else:
|
else:
|
||||||
|
@ -328,11 +318,6 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal
|
||||||
|
|
||||||
import yunohost.domain
|
import yunohost.domain
|
||||||
|
|
||||||
# Check if old letsencrypt_ynh is installed
|
|
||||||
# TODO / FIXME - Remove this in the future once the letsencrypt app is
|
|
||||||
# not used anymore
|
|
||||||
_check_old_letsencrypt_app()
|
|
||||||
|
|
||||||
# If no domains given, consider all yunohost domains with Let's Encrypt
|
# If no domains given, consider all yunohost domains with Let's Encrypt
|
||||||
# certificates
|
# certificates
|
||||||
if domain_list == []:
|
if domain_list == []:
|
||||||
|
@ -355,7 +340,7 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal
|
||||||
|
|
||||||
domain_list.append(domain)
|
domain_list.append(domain)
|
||||||
|
|
||||||
if len(domain_list) == 0:
|
if len(domain_list) == 0 and not email:
|
||||||
logger.info("No certificate needs to be renewed.")
|
logger.info("No certificate needs to be renewed.")
|
||||||
|
|
||||||
# Else, validate the domain list given
|
# Else, validate the domain list given
|
||||||
|
@ -430,18 +415,6 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal
|
||||||
# Back-end stuff #
|
# Back-end stuff #
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
def _check_old_letsencrypt_app():
|
|
||||||
import yunohost.domain
|
|
||||||
|
|
||||||
installedAppIds = [app["id"] for app in yunohost.app.app_list(installed=True)["apps"]]
|
|
||||||
|
|
||||||
if "letsencrypt" not in installedAppIds:
|
|
||||||
return
|
|
||||||
|
|
||||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
|
||||||
'certmanager_old_letsencrypt_app_detected'))
|
|
||||||
|
|
||||||
|
|
||||||
def _install_cron():
|
def _install_cron():
|
||||||
cron_job_file = "/etc/cron.daily/yunohost-certificate-renew"
|
cron_job_file = "/etc/cron.daily/yunohost-certificate-renew"
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ class MyMigration(Migration):
|
||||||
# with /etc/lsb-release for instance -_-)
|
# with /etc/lsb-release for instance -_-)
|
||||||
# Instead, we rely on /etc/os-release which should be the raw info from
|
# Instead, we rely on /etc/os-release which should be the raw info from
|
||||||
# the distribution...
|
# the distribution...
|
||||||
return int(check_output("grep VERSION_ID /etc/os-release | tr '\"' ' ' | cut -d ' ' -f2"))
|
return int(check_output("grep VERSION_ID /etc/os-release | head -n 1 | tr '\"' ' ' | cut -d ' ' -f2"))
|
||||||
|
|
||||||
def yunohost_major_version(self):
|
def yunohost_major_version(self):
|
||||||
return int(get_installed_version("yunohost").split('.')[0])
|
return int(get_installed_version("yunohost").split('.')[0])
|
||||||
|
|
|
@ -209,6 +209,11 @@ def domain_dns_conf(domain, ttl=None):
|
||||||
for record in dns_conf["mail"]:
|
for record in dns_conf["mail"]:
|
||||||
result += "\n{name} {ttl} IN {type} {value}".format(**record)
|
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
|
is_cli = True if msettings.get('interface') == 'cli' else False
|
||||||
if is_cli:
|
if is_cli:
|
||||||
logger.info(m18n.n("domain_dns_conf_is_just_a_recommendation"))
|
logger.info(m18n.n("domain_dns_conf_is_just_a_recommendation"))
|
||||||
|
@ -334,6 +339,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": "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}
|
{"type": "TXT", "name": "_dmarc", "value": "\"v=DMARC1; p=none\"", "ttl": 3600}
|
||||||
],
|
],
|
||||||
|
"extra": [
|
||||||
|
{"type": "CAA", "name": "@", "value": "128 issue 'letsencrypt.org", "ttl": 3600},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -387,10 +395,16 @@ def _build_dns_conf(domain, ttl=3600):
|
||||||
["_dmarc", ttl, "TXT", '"v=DMARC1; p=none"'],
|
["_dmarc", ttl, "TXT", '"v=DMARC1; p=none"'],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Extra
|
||||||
|
extra = [
|
||||||
|
["@", ttl, "CAA", "128 issue 'letsencrypt.org'"]
|
||||||
|
]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"basic": [{"name": name, "ttl": ttl, "type": type_, "value": value} for name, ttl, type_, value in basic],
|
"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],
|
"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],
|
"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],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,8 @@ from moulinette.core import MoulinetteError
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
from moulinette.utils.filesystem import read_file, write_to_file, rm
|
from moulinette.utils.filesystem import read_file, write_to_file, rm
|
||||||
from moulinette.utils.network import download_json
|
from moulinette.utils.network import download_json
|
||||||
|
from moulinette.utils.process import check_output
|
||||||
|
|
||||||
|
|
||||||
from yunohost.domain import _get_maindomain, _build_dns_conf
|
from yunohost.domain import _get_maindomain, _build_dns_conf
|
||||||
from yunohost.utils.network import get_public_ip
|
from yunohost.utils.network import get_public_ip
|
||||||
|
@ -44,8 +46,6 @@ from yunohost.log import is_unit_operation
|
||||||
|
|
||||||
logger = getActionLogger('yunohost.dyndns')
|
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'
|
DYNDNS_ZONE = '/etc/yunohost/dyndns/zone'
|
||||||
|
|
||||||
RE_DYNDNS_PRIVATE_KEY_MD5 = re.compile(
|
RE_DYNDNS_PRIVATE_KEY_MD5 = re.compile(
|
||||||
|
@ -96,7 +96,7 @@ def _dyndns_available(provider, domain):
|
||||||
domain -- The full domain that you'd like.. e.g. "foo.nohost.me"
|
domain -- The full domain that you'd like.. e.g. "foo.nohost.me"
|
||||||
|
|
||||||
Returns:
|
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 ..."
|
logger.debug("Checking if domain %s is available on %s ..."
|
||||||
% (domain, provider))
|
% (domain, provider))
|
||||||
|
@ -193,32 +193,6 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
|
||||||
|
|
||||||
old_ipv4, old_ipv6 = (None, None) # (default values)
|
old_ipv4, old_ipv6 = (None, None) # (default values)
|
||||||
|
|
||||||
if os.path.isfile(OLD_IPV4_FILE):
|
|
||||||
old_ipv4 = read_file(OLD_IPV4_FILE).rstrip()
|
|
||||||
|
|
||||||
if os.path.isfile(OLD_IPV6_FILE):
|
|
||||||
old_ipv6 = read_file(OLD_IPV6_FILE).rstrip()
|
|
||||||
|
|
||||||
# Get current IPv4 and IPv6
|
|
||||||
ipv4_ = get_public_ip()
|
|
||||||
ipv6_ = get_public_ip(6)
|
|
||||||
|
|
||||||
if ipv4 is None:
|
|
||||||
ipv4 = ipv4_
|
|
||||||
|
|
||||||
if ipv6 is None:
|
|
||||||
ipv6 = ipv6_
|
|
||||||
|
|
||||||
logger.debug("Old IPv4/v6 are (%s, %s)" % (old_ipv4, old_ipv6))
|
|
||||||
logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6))
|
|
||||||
|
|
||||||
# no need to update
|
|
||||||
if old_ipv4 == ipv4 and old_ipv6 == ipv6:
|
|
||||||
logger.info("No updated needed.")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
logger.info("Updated needed, going on...")
|
|
||||||
|
|
||||||
# If domain is not given, try to guess it from keys available...
|
# If domain is not given, try to guess it from keys available...
|
||||||
if domain is None:
|
if domain is None:
|
||||||
(domain, key) = _guess_current_dyndns_domain(dyn_host)
|
(domain, key) = _guess_current_dyndns_domain(dyn_host)
|
||||||
|
@ -262,7 +236,32 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
|
||||||
'zone %s' % 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()
|
||||||
|
ipv6_ = get_public_ip(6)
|
||||||
|
|
||||||
|
if ipv4 is None:
|
||||||
|
ipv4 = ipv4_
|
||||||
|
|
||||||
|
if ipv6 is None:
|
||||||
|
ipv6 = ipv6_
|
||||||
|
|
||||||
|
logger.debug("Old IPv4/v6 are (%s, %s)" % (old_ipv4, old_ipv6))
|
||||||
|
logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6))
|
||||||
|
|
||||||
|
# no need to update
|
||||||
|
if old_ipv4 == ipv4 and old_ipv6 == ipv6:
|
||||||
|
logger.info("No updated needed.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
logger.info("Updated needed, going on...")
|
||||||
|
|
||||||
dns_conf = _build_dns_conf(domain)
|
dns_conf = _build_dns_conf(domain)
|
||||||
|
del dns_conf["extra"] # Ignore records from the 'extra' category
|
||||||
|
|
||||||
# Delete the old records for all domain/subdomains
|
# Delete the old records for all domain/subdomains
|
||||||
|
|
||||||
|
@ -304,18 +303,11 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
|
||||||
command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE]
|
command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE]
|
||||||
subprocess.check_call(command)
|
subprocess.check_call(command)
|
||||||
except subprocess.CalledProcessError:
|
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,
|
raise MoulinetteError(errno.EPERM,
|
||||||
m18n.n('dyndns_ip_update_failed'))
|
m18n.n('dyndns_ip_update_failed'))
|
||||||
|
|
||||||
logger.success(m18n.n('dyndns_ip_updated'))
|
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():
|
def dyndns_installcron():
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -281,7 +281,7 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None,
|
||||||
hook_args = pre_callback(name=name, priority=priority,
|
hook_args = pre_callback(name=name, priority=priority,
|
||||||
path=path, args=args)
|
path=path, args=args)
|
||||||
hook_exec(path, args=hook_args, chdir=chdir, env=env,
|
hook_exec(path, args=hook_args, chdir=chdir, env=env,
|
||||||
no_trace=no_trace, raise_on_error=True, user="root")
|
no_trace=no_trace, raise_on_error=True)
|
||||||
except MoulinetteError as e:
|
except MoulinetteError as e:
|
||||||
state = 'failed'
|
state = 'failed'
|
||||||
logger.error(e.strerror, exc_info=1)
|
logger.error(e.strerror, exc_info=1)
|
||||||
|
@ -298,7 +298,7 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None,
|
||||||
|
|
||||||
|
|
||||||
def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
|
def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
|
||||||
chdir=None, env=None, user="admin", stdout_callback=None,
|
chdir=None, env=None, user="root", stdout_callback=None,
|
||||||
stderr_callback=None):
|
stderr_callback=None):
|
||||||
"""
|
"""
|
||||||
Execute hook from a file with arguments
|
Execute hook from a file with arguments
|
||||||
|
|
|
@ -50,7 +50,7 @@ MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock"
|
||||||
logger = log.getActionLogger('yunohost.service')
|
logger = log.getActionLogger('yunohost.service')
|
||||||
|
|
||||||
|
|
||||||
def service_add(name, status=None, log=None, runlevel=None):
|
def service_add(name, status=None, log=None, runlevel=None, need_lock=False, description=None):
|
||||||
"""
|
"""
|
||||||
Add a custom service
|
Add a custom service
|
||||||
|
|
||||||
|
@ -59,7 +59,8 @@ def service_add(name, status=None, log=None, runlevel=None):
|
||||||
status -- Custom status command
|
status -- Custom status command
|
||||||
log -- Absolute path to log file to display
|
log -- Absolute path to log file to display
|
||||||
runlevel -- Runlevel priority of the service
|
runlevel -- Runlevel priority of the service
|
||||||
|
need_lock -- Use this option to prevent deadlocks if the service does invoke yunohost commands.
|
||||||
|
description -- description of the service
|
||||||
"""
|
"""
|
||||||
services = _get_services()
|
services = _get_services()
|
||||||
|
|
||||||
|
@ -74,6 +75,12 @@ def service_add(name, status=None, log=None, runlevel=None):
|
||||||
if runlevel is not None:
|
if runlevel is not None:
|
||||||
services[name]['runlevel'] = runlevel
|
services[name]['runlevel'] = runlevel
|
||||||
|
|
||||||
|
if need_lock:
|
||||||
|
services[name]['need_lock'] = True
|
||||||
|
|
||||||
|
if description is not None:
|
||||||
|
services[name]['description'] = description
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_save_services(services)
|
_save_services(services)
|
||||||
except:
|
except:
|
||||||
|
@ -251,6 +258,9 @@ def service_status(names=[]):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
translation_key = "service_description_%s" % name
|
translation_key = "service_description_%s" % name
|
||||||
|
if "description" in services[name] is not None:
|
||||||
|
description = services[name].get("description")
|
||||||
|
else:
|
||||||
description = m18n.n(translation_key)
|
description = m18n.n(translation_key)
|
||||||
|
|
||||||
# that mean that we don't have a translation for this string
|
# that mean that we don't have a translation for this string
|
||||||
|
|
|
@ -29,12 +29,16 @@ SETTINGS_PATH_OTHER_LOCATION = "/etc/yunohost/settings-%s.json"
|
||||||
# * string
|
# * string
|
||||||
# * enum (in form a python list)
|
# * enum (in form a python list)
|
||||||
|
|
||||||
# we don't store the value in default options
|
|
||||||
DEFAULTS = OrderedDict([
|
DEFAULTS = OrderedDict([
|
||||||
("example.bool", {"type": "bool", "default": True}),
|
("example.bool", {"type": "bool", "default": True}),
|
||||||
("example.int", {"type": "int", "default": 42}),
|
("example.int", {"type": "int", "default": 42}),
|
||||||
("example.string", {"type": "string", "default": "yolo swag"}),
|
("example.string", {"type": "string", "default": "yolo swag"}),
|
||||||
("example.enum", {"type": "enum", "default": "a", "choices": ["a", "b", "c"]}),
|
("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}),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@ -90,6 +94,9 @@ def settings_set(key, value):
|
||||||
received_type=type(value).__name__, expected_type=key_type))
|
received_type=type(value).__name__, expected_type=key_type))
|
||||||
elif key_type == "int":
|
elif key_type == "int":
|
||||||
if not isinstance(value, int) or isinstance(value, bool):
|
if not isinstance(value, int) or isinstance(value, bool):
|
||||||
|
if isinstance(value, str):
|
||||||
|
value=int(value)
|
||||||
|
else:
|
||||||
raise MoulinetteError(errno.EINVAL, m18n.n(
|
raise MoulinetteError(errno.EINVAL, m18n.n(
|
||||||
'global_settings_bad_type_for_setting', setting=key,
|
'global_settings_bad_type_for_setting', setting=key,
|
||||||
received_type=type(value).__name__, expected_type=key_type))
|
received_type=type(value).__name__, expected_type=key_type))
|
||||||
|
|
|
@ -32,6 +32,7 @@ import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
import pwd
|
import pwd
|
||||||
import socket
|
import socket
|
||||||
|
import cracklib
|
||||||
from xmlrpclib import Fault
|
from xmlrpclib import Fault
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
@ -127,8 +128,13 @@ def tools_adminpw(auth, new_password):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from yunohost.user import _hash_user_password
|
from yunohost.user import _hash_user_password
|
||||||
|
from yunohost.utils.password import assert_password_is_strong_enough
|
||||||
import spwd
|
import spwd
|
||||||
|
|
||||||
|
assert_password_is_strong_enough("admin", new_password)
|
||||||
|
|
||||||
new_hash = _hash_user_password(new_password)
|
new_hash = _hash_user_password(new_password)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
auth.update("cn=admin", { "userPassword": new_hash, })
|
auth.update("cn=admin", { "userPassword": new_hash, })
|
||||||
except:
|
except:
|
||||||
|
@ -263,7 +269,8 @@ def _is_inside_container():
|
||||||
|
|
||||||
|
|
||||||
@is_unit_operation()
|
@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
|
YunoHost post-install
|
||||||
|
|
||||||
|
@ -274,6 +281,8 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
|
||||||
password -- YunoHost admin password
|
password -- YunoHost admin password
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from yunohost.utils.password import assert_password_is_strong_enough
|
||||||
|
|
||||||
dyndns_provider = "dyndns.yunohost.org"
|
dyndns_provider = "dyndns.yunohost.org"
|
||||||
|
|
||||||
# Do some checks at first
|
# Do some checks at first
|
||||||
|
@ -281,6 +290,10 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
|
||||||
raise MoulinetteError(errno.EPERM,
|
raise MoulinetteError(errno.EPERM,
|
||||||
m18n.n('yunohost_already_installed'))
|
m18n.n('yunohost_already_installed'))
|
||||||
|
|
||||||
|
# Check password
|
||||||
|
if not force_password:
|
||||||
|
assert_password_is_strong_enough("admin", password)
|
||||||
|
|
||||||
if not ignore_dyndns:
|
if not ignore_dyndns:
|
||||||
# Check if yunohost dyndns can handle the given domain
|
# Check if yunohost dyndns can handle the given domain
|
||||||
# (i.e. is it a .nohost.me ? a .noho.st ?)
|
# (i.e. is it a .nohost.me ? a .noho.st ?)
|
||||||
|
@ -312,6 +325,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
|
||||||
else:
|
else:
|
||||||
dyndns = False
|
dyndns = False
|
||||||
|
|
||||||
|
|
||||||
operation_logger.start()
|
operation_logger.start()
|
||||||
logger.info(m18n.n('yunohost_installing'))
|
logger.info(m18n.n('yunohost_installing'))
|
||||||
|
|
||||||
|
@ -371,7 +385,6 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False):
|
||||||
service_regen_conf(['ssl'], force=True)
|
service_regen_conf(['ssl'], force=True)
|
||||||
ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA'
|
ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA'
|
||||||
commands = [
|
commands = [
|
||||||
'echo "01" > %s/serial' % ssl_dir,
|
|
||||||
'rm %s/index.txt' % ssl_dir,
|
'rm %s/index.txt' % ssl_dir,
|
||||||
'touch %s/index.txt' % ssl_dir,
|
'touch %s/index.txt' % ssl_dir,
|
||||||
'cp %s/openssl.cnf %s/openssl.ca.cnf' % (ssl_dir, ssl_dir),
|
'cp %s/openssl.cnf %s/openssl.ca.cnf' % (ssl_dir, ssl_dir),
|
||||||
|
@ -1059,3 +1072,4 @@ class Migration(object):
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
return m18n.n("migration_description_%s" % self.id)
|
return m18n.n("migration_description_%s" % self.id)
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ import crypt
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import cracklib
|
||||||
|
|
||||||
from moulinette import m18n
|
from moulinette import m18n
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
|
@ -116,6 +117,10 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
|
||||||
from yunohost.domain import domain_list, _get_maindomain
|
from yunohost.domain import domain_list, _get_maindomain
|
||||||
from yunohost.hook import hook_callback
|
from yunohost.hook import hook_callback
|
||||||
from yunohost.app import app_ssowatconf
|
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
|
# Validate uniqueness of username and mail in LDAP
|
||||||
auth.validate_uniqueness({
|
auth.validate_uniqueness({
|
||||||
|
@ -128,6 +133,17 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
|
||||||
if username in all_existing_usernames:
|
if username in all_existing_usernames:
|
||||||
raise MoulinetteError(errno.EEXIST, m18n.n('system_username_exists'))
|
raise MoulinetteError(errno.EEXIST, m18n.n('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 MoulinetteError(errno.EEXIST,m18n.n('mail_unavailable'))
|
||||||
|
|
||||||
# Check that the mail domain exists
|
# Check that the mail domain exists
|
||||||
if mail.split("@")[1] not in domain_list(auth)['domains']:
|
if mail.split("@")[1] not in domain_list(auth)['domains']:
|
||||||
raise MoulinetteError(errno.EINVAL,
|
raise MoulinetteError(errno.EINVAL,
|
||||||
|
@ -166,13 +182,6 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
|
||||||
|
|
||||||
# If it is the first user, add some aliases
|
# If it is the first user, add some aliases
|
||||||
if not auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=*'):
|
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
|
attr_dict['mail'] = [attr_dict['mail']] + aliases
|
||||||
|
|
||||||
# If exists, remove the redirection from the SSO
|
# If exists, remove the redirection from the SSO
|
||||||
|
@ -279,6 +288,7 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
|
||||||
"""
|
"""
|
||||||
from yunohost.domain import domain_list
|
from yunohost.domain import domain_list
|
||||||
from yunohost.app import app_ssowatconf
|
from yunohost.app import app_ssowatconf
|
||||||
|
from yunohost.utils.password import assert_password_is_strong_enough
|
||||||
|
|
||||||
attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop']
|
attrs_to_fetch = ['givenName', 'sn', 'mail', 'maildrop']
|
||||||
new_attr_dict = {}
|
new_attr_dict = {}
|
||||||
|
@ -303,14 +313,27 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
|
||||||
new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + lastname
|
new_attr_dict['cn'] = new_attr_dict['displayName'] = firstname + ' ' + lastname
|
||||||
|
|
||||||
if change_password:
|
if change_password:
|
||||||
|
# Ensure sufficiently complex password
|
||||||
|
assert_password_is_strong_enough("user", password)
|
||||||
|
|
||||||
new_attr_dict['userPassword'] = _hash_user_password(change_password)
|
new_attr_dict['userPassword'] = _hash_user_password(change_password)
|
||||||
|
|
||||||
if mail:
|
if mail:
|
||||||
|
main_domain = _get_maindomain()
|
||||||
|
aliases = [
|
||||||
|
'root@' + main_domain,
|
||||||
|
'admin@' + main_domain,
|
||||||
|
'webmaster@' + main_domain,
|
||||||
|
'postmaster@' + main_domain,
|
||||||
|
]
|
||||||
auth.validate_uniqueness({'mail': mail})
|
auth.validate_uniqueness({'mail': mail})
|
||||||
if mail[mail.find('@') + 1:] not in domains:
|
if mail[mail.find('@') + 1:] not in domains:
|
||||||
raise MoulinetteError(errno.EINVAL,
|
raise MoulinetteError(errno.EINVAL,
|
||||||
m18n.n('mail_domain_unknown',
|
m18n.n('mail_domain_unknown',
|
||||||
domain=mail[mail.find('@') + 1:]))
|
domain=mail[mail.find('@') + 1:]))
|
||||||
|
if mail in aliases:
|
||||||
|
raise MoulinetteError(errno.EEXIST,m18n.n('mail_unavailable'))
|
||||||
|
|
||||||
del user['mail'][0]
|
del user['mail'][0]
|
||||||
new_attr_dict['mail'] = [mail] + user['mail']
|
new_attr_dict['mail'] = [mail] + user['mail']
|
||||||
|
|
||||||
|
|
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])
|
||||||
|
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 errno
|
||||||
|
import logging
|
||||||
|
from moulinette import m18n
|
||||||
|
from moulinette.core import MoulinetteError
|
||||||
|
from moulinette.utils.log import getActionLogger
|
||||||
|
|
||||||
|
logger = logging.getLogger('yunohost.utils.password')
|
||||||
|
|
||||||
|
status, msg = self.validation_summary(password)
|
||||||
|
if status == "error":
|
||||||
|
raise MoulinetteError(1, m18n.n(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)
|
Loading…
Add table
Reference in a new issue