diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 05aafe43b..23a0075de 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,23 +1,32 @@
stages:
- postinstall
- tests
+ - lint
-.tests:
+########################################
+# POSTINSTALL
+########################################
+
+postinstall:
+ image: before-postinstall
+ stage: postinstall
+ script:
+ - apt install --no-install-recommends -y $(cat debian/control | grep "^Depends" -A50 | grep "Recommends:" -B50 | grep "^ *," | grep -o -P "[\w\-]{3,}")
+ - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns
+
+########################################
+# TESTS
+########################################
+
+.test-stage:
image: after-postinstall
+ stage: tests
before_script:
- apt-get install python-pip -y
- mkdir -p .pip
- pip install -U pip
- hash -d pip
- pip --cache-dir=.pip install pytest pytest-sugar pytest-mock requests-mock mock
- - pushd src/yunohost/tests
- - >
- if [ ! -d "./apps" ]; then
- git clone https://github.com/YunoHost/test_apps ./apps
- fi
- - cd apps
- - git pull > /dev/null 2>&1
- - popd
- export PYTEST_ADDOPTS="--color=yes"
cache:
paths:
@@ -25,77 +34,109 @@ stages:
- src/yunohost/tests/apps
key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
-postinstall:
- image: before-postinstall
- stage: postinstall
- script:
- - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns
-
root-tests:
- extends: .tests
- stage: tests
+ extends: .test-stage
script:
- py.test tests
test-apps:
- extends: .tests
- stage: tests
+ extends: .test-stage
script:
- cd src/yunohost
- py.test tests/test_apps.py
test-appscatalog:
- extends: .tests
- stage: tests
+ extends: .test-stage
script:
- cd src/yunohost
- py.test tests/test_appscatalog.py
test-appurl:
- extends: .tests
- stage: tests
+ extends: .test-stage
script:
- cd src/yunohost
- py.test tests/test_appurl.py
+test-apps-arguments-parsing:
+ extends: .test-stage
+ script:
+ - cd src/yunohost
+ - py.test tests/test_apps_arguments_parsing.py
+
test-backuprestore:
- extends: .tests
- stage: tests
+ extends: .test-stage
script:
- cd src/yunohost
- py.test tests/test_backuprestore.py
test-changeurl:
- extends: .tests
- stage: tests
+ extends: .test-stage
script:
- cd src/yunohost
- py.test tests/test_changeurl.py
test-permission:
- extends: .tests
- stage: tests
+ extends: .test-stage
script:
- cd src/yunohost
- py.test tests/test_permission.py
test-settings:
- extends: .tests
- stage: tests
+ extends: .test-stage
script:
- cd src/yunohost
- py.test tests/test_settings.py
test-user-group:
- extends: .tests
- stage: tests
+ extends: .test-stage
script:
- cd src/yunohost
- py.test tests/test_user-group.py
test-regenconf:
- extends: .tests
- stage: tests
+ extends: .test-stage
script:
- cd src/yunohost
- py.test tests/test_regenconf.py
+
+test-service:
+ extends: .test-stage
+ script:
+ - cd src/yunohost
+ - py.test tests/test_service.py
+
+########################################
+# LINTER
+########################################
+
+.lint-stage:
+ image: before-postinstall
+ stage: lint
+ before_script:
+ - apt-get install python-pip -y
+ - mkdir -p .pip
+ - pip install -U pip
+ - hash -d pip
+ - pip --cache-dir=.pip install tox
+ cache:
+ paths:
+ - .pip
+ - .tox
+ key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
+
+lint:
+ extends: .lint-stage
+ allow_failure: true
+ script:
+ - tox -e lint
+
+invalidcode:
+ extends: .lint-stage
+ script:
+ - tox -e invalidcode
+
+# Disabled, waiting for buster
+#format-check:
+# extends: .lint-stage
+# script:
+# - black --check --diff
diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml
index ad1fd11a3..5bbcecb62 100644
--- a/data/actionsmap/yunohost.yml
+++ b/data/actionsmap/yunohost.yml
@@ -1005,7 +1005,6 @@ service:
choices:
- file
- systemd
- default: file
--test_status:
help: Specify a custom bash command to check the status of the service. Note that it only makes sense to specify this if the corresponding systemd service does not return the proper information already.
--test_conf:
@@ -1668,6 +1667,10 @@ log:
--share:
help: Share the full log using yunopaste
action: store_true
+ -i:
+ full: --filter-irrelevant
+ help: Do not show some lines deemed not relevant (like set +x or helper argument parsing)
+ action: store_true
#############################
@@ -1697,6 +1700,9 @@ diagnosis:
--share:
help: Share the logs using yunopaste
action: store_true
+ --human-readable:
+ help: Show a human-readable output
+ action: store_true
run:
action_help: Run diagnosis
@@ -1711,6 +1717,9 @@ diagnosis:
--except-if-never-ran-yet:
help: Don't run anything if diagnosis never ran yet ... (this is meant to be used by the webadmin)
action: store_true
+ --email:
+ help: Send an email to root with issues found (this is meant to be used by cron job)
+ action: store_true
ignore:
action_help: Configure some diagnosis results to be ignored and therefore not considered as actual issues
diff --git a/data/helpers.d/apt b/data/helpers.d/apt
index 9e3f26b90..c6621d814 100644
--- a/data/helpers.d/apt
+++ b/data/helpers.d/apt
@@ -10,6 +10,7 @@
# Requires YunoHost version 3.3.1 or higher.
ynh_wait_dpkg_free() {
local try
+ set +o xtrace # set +x
# With seq 1 17, timeout will be almost 30 minutes
for try in `seq 1 17`
do
@@ -32,13 +33,16 @@ ynh_wait_dpkg_free() {
then
# If so, that a remaining of dpkg.
ynh_print_err "E: dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem."
+ set -o xtrace # set -x
return 1
fi
done 9<<< "$(ls -1 $dpkg_dir)"
+ set -o xtrace # set -x
return 0
fi
done
echo "apt still used, but timeout reached !"
+ set -o xtrace # set -x
}
# Check either a package is installed or not
@@ -96,7 +100,7 @@ ynh_package_version() {
# Requires YunoHost version 2.4.0.3 or higher.
ynh_apt() {
ynh_wait_dpkg_free
- LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get --assume-yes $@
+ LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get --assume-yes --quiet -o=Dpkg::Use-Pty=0 $@
}
# Update package index files
@@ -188,20 +192,38 @@ ynh_package_install_from_equivs () {
(cd "$TMPDIR"
LC_ALL=C equivs-build ./control 1> /dev/null
dpkg --force-depends --install "./${pkgname}_${pkgversion}_all.deb" 2>&1)
- # If install fails we use "apt-get check" to try to debug and diagnose possible unmet dependencies
- # Note the use of { } which allows to group commands without starting a subshell (otherwise the ynh_die wouldn't exit the current shell).
- # Be careful with the syntax : the semicolon + space at the end is important!
- ynh_package_install --fix-broken || \
- { # If the installation failed
+ # Let's try to see if install will work using dry-run. It it fails,
+ # it could be because the pinning of sury is blocking some package install
+ # c.f. for example: https://github.com/YunoHost/issues/issues/1563#issuecomment-623406509
+ # ... In that case, we use an ugly hack were we'll use a tweaked
+ # preferences.d directory with looser contrains for sury...
+ if ! ynh_package_install --fix-broken --dry-run >/dev/null 2>&1 && [ -e /etc/apt/preferences.d/extra_php_version ]
+ then
+ cp -r /etc/apt/preferences.d/ /etc/apt/preferences.d.tmp/
+ sed 's/^Pin-Priority: .*/Pin-Priority: 600/g' -i /etc/apt/preferences.d.tmp/extra_php_version
+ local apt_tweaks='--option Dir::Etc::preferencesparts=preferences.d.tmp'
+ # Try a dry-run again to see if that fixes the issue ...
+ # If it did not, then that's probably not related to sury.
+ ynh_package_install $apt_tweaks --fix-broken --dry-run >/dev/null 2>&1 || apt_tweaks=""
+ else
+ local apt_tweaks=""
+ fi
+
+ # Try to install for real
+ ynh_package_install $apt_tweaks --fix-broken || \
+ { # If the installation failed
+ # (the following is ran inside { } to not start a subshell otherwise ynh_die wouldnt exit the original process)
+ rm --recursive --force /etc/apt/preferences.d.tmp/
# Get the list of dependencies from the deb
local dependencies="$(dpkg --info "$TMPDIR/${pkgname}_${pkgversion}_all.deb" | grep Depends | \
sed 's/^ Depends: //' | sed 's/,//g')"
# Fake an install of those dependencies to see the errors
# The sed command here is, Print only from '--fix-broken' to the end.
- ynh_package_install $dependencies --dry-run | sed --quiet '/--fix-broken/,$p' >&2
+ ynh_package_install $apt_tweaks $dependencies --dry-run | sed --quiet '/--fix-broken/,$p' >&2
ynh_die --message="Unable to install dependencies"; }
[[ -n "$TMPDIR" ]] && rm --recursive --force $TMPDIR # Remove the temp dir.
+ rm --recursive --force /etc/apt/preferences.d.tmp/
# check if the package is actually installed
ynh_package_is_installed "$pkgname"
@@ -266,7 +288,7 @@ ynh_install_app_dependencies () {
# Pin this sury repository to prevent sury of doing shit
ynh_pin_repo --package="*" --pin="origin \"packages.sury.org\"" --priority=200 --name=extra_php_version
- ynh_pin_repo --package="php${$YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append
+ ynh_pin_repo --package="php${YNH_DEFAULT_PHP_VERSION}*" --pin="origin \"packages.sury.org\"" --priority=600 --name=extra_php_version --append
fi
fi
fi
@@ -331,8 +353,6 @@ ynh_remove_app_dependencies () {
ynh_package_autopurge ${dep_app}-ynh-deps # Remove the fake package and its dependencies if they not still used.
}
-#=================================================
-
# Install packages from an extra repository properly.
#
# usage: ynh_install_extra_app_dependencies --repo="repo" --package="dep1 dep2" [--key=key_url] [--name=name]
@@ -438,7 +458,8 @@ ynh_install_extra_repo () {
if [ -n "$key" ]
then
mkdir --parents "/etc/apt/trusted.gpg.d"
- wget --quiet "$key" --output-document=- | gpg --dearmor | $wget_append /etc/apt/trusted.gpg.d/$name.gpg > /dev/null
+ # Timeout option is here to enforce the timeout on dns query and tcp connect (c.f. man wget)
+ wget --timeout 900 --quiet "$key" --output-document=- | gpg --dearmor | $wget_append /etc/apt/trusted.gpg.d/$name.gpg > /dev/null
fi
# Update the list of package with the new repo
diff --git a/data/helpers.d/fail2ban b/data/helpers.d/fail2ban
index d8777a16d..f9bdd89b2 100644
--- a/data/helpers.d/fail2ban
+++ b/data/helpers.d/fail2ban
@@ -134,7 +134,7 @@ EOF
ynh_systemd_action --service_name=fail2ban --action=reload --line_match="(Started|Reloaded) Fail2Ban Service" --log_path=systemd
- local fail2ban_error="$(journalctl --unit=fail2ban | tail --lines=50 | grep "WARNING.*$app.*")"
+ local fail2ban_error="$(journalctl --no-hostname --unit=fail2ban | tail --lines=50 | grep "WARNING.*$app.*")"
if [[ -n "$fail2ban_error" ]]
then
ynh_print_err --message="Fail2ban failed to load the jail for $app"
diff --git a/data/helpers.d/hardware b/data/helpers.d/hardware
index 6702a8548..0771c81d8 100644
--- a/data/helpers.d/hardware
+++ b/data/helpers.d/hardware
@@ -25,17 +25,17 @@ ynh_get_ram () {
free=${free:-0}
total=${total:-0}
- local total_ram=$(vmstat --stats --unit M | grep "total memory" | awk '{print $1}')
- local total_swap=$(vmstat --stats --unit M | grep "total swap" | awk '{print $1}')
- local total_ram_swap=$(( total_ram + total_swap ))
-
- local free_ram=$(vmstat --stats --unit M | grep "free memory" | awk '{print $1}')
- local free_swap=$(vmstat --stats --unit M | grep "free swap" | awk '{print $1}')
- local free_ram_swap=$(( free_ram + free_swap ))
-
- # Use the total amount of ram
- if [ $free -eq 1 ]
+ if [ $free -eq $total ]
then
+ ynh_print_warn --message="You have to choose --free or --total when using ynh_get_ram"
+ ram=0
+ # Use the total amount of ram
+ elif [ $free -eq 1 ]
+ then
+ local free_ram=$(vmstat --stats --unit M | grep "free memory" | awk '{print $1}')
+ local free_swap=$(vmstat --stats --unit M | grep "free swap" | awk '{print $1}')
+ local free_ram_swap=$(( free_ram + free_swap ))
+
# Use the total amount of free ram
local ram=$free_ram_swap
if [ $ignore_swap -eq 1 ]
@@ -49,6 +49,10 @@ ynh_get_ram () {
fi
elif [ $total -eq 1 ]
then
+ local total_ram=$(vmstat --stats --unit M | grep "total memory" | awk '{print $1}')
+ local total_swap=$(vmstat --stats --unit M | grep "total swap" | awk '{print $1}')
+ local total_ram_swap=$(( total_ram + total_swap ))
+
local ram=$total_ram_swap
if [ $ignore_swap -eq 1 ]
then
@@ -59,9 +63,6 @@ ynh_get_ram () {
# Use only the amount of free swap
ram=$total_swap
fi
- else
- ynh_print_warn --message="You have to choose --free or --total when using ynh_get_ram"
- ram=0
fi
echo $ram
diff --git a/data/helpers.d/logging b/data/helpers.d/logging
index c79090e25..45b5b7e67 100644
--- a/data/helpers.d/logging
+++ b/data/helpers.d/logging
@@ -80,7 +80,7 @@ ynh_print_warn () {
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
- ynh_print_log "\e[93m\e[1m[WARN]\e[0m ${message}" >&2
+ ynh_print_log "${message}" >&2
}
# Print an error on stderr
@@ -97,7 +97,7 @@ ynh_print_err () {
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
- ynh_print_log "\e[91m\e[1m[ERR]\e[0m ${message}" >&2
+ ynh_print_log "[Error] ${message}" >&2
}
# Execute a command and print the result as an error
@@ -216,7 +216,7 @@ base_time=$(date +%s)
# | arg: -m, --message= - The text to print
# | arg: -w, --weight= - The weight for this progression. This value is 1 by default. Use a bigger value for a longer part of the script.
# | arg: -t, --time - Print the execution time since the last call to this helper. Especially usefull to define weights. The execution time is given for the duration since the previous call. So the weight should be applied to this previous call.
-# | arg: -l, --last - Use for the last call of the helper, to fill te progression bar.
+# | arg: -l, --last - Use for the last call of the helper, to fill the progression bar.
#
# Requires YunoHost version 3.5.0 or higher.
ynh_script_progression () {
@@ -332,7 +332,7 @@ ynh_debug () {
if [ -n "$message" ]
then
- ynh_print_log "\e[34m\e[1m[DEBUG]\e[0m ${message}" >&2
+ ynh_print_log "[Debug] ${message}" >&2
fi
if [ "$trace" == "1" ]
diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs
index efb50ae37..3ad0c400f 100644
--- a/data/helpers.d/nodejs
+++ b/data/helpers.d/nodejs
@@ -28,14 +28,38 @@ SOURCE_SUM=3983fa3f00d4bf85ba8e21f1a590f6e28938093abe0bb950aeea52b1717471fc" > "
# Load the version of node for an app, and set variables.
#
# ynh_use_nodejs has to be used in any app scripts before using node for the first time.
+# This helper will provide alias and variables to use in your scripts.
#
-# 2 variables are available:
-# - $nodejs_path: The absolute path of node for the chosen version.
+# To use npm or node, use the alias `ynh_npm` and `ynh_node`
+# Those alias will use the correct version installed for the app
+# For example: use `ynh_npm install` instead of `npm install`
+#
+# With `sudo` or `ynh_exec_as`, use instead the fallback variables `$ynh_npm` and `$ynh_node`
+# And propagate $PATH to sudo with $ynh_node_load_PATH
+# Exemple: `ynh_exec_as $app $ynh_node_load_PATH $ynh_npm install`
+#
+# $PATH contains the path of the requested version of node.
+# However, $PATH is duplicated into $node_PATH to outlast any manipulation of $PATH
+# You can use the variable `$ynh_node_load_PATH` to quickly load your node version
+# in $PATH for an usage into a separate script.
+# Exemple: $ynh_node_load_PATH $final_path/script_that_use_npm.sh`
+#
+#
+# Finally, to start a nodejs service with the correct version, 2 solutions
+# Either the app is dependent of node or npm, but does not called it directly.
+# In such situation, you need to load PATH
+# `Environment="__NODE_ENV_PATH__"`
+# `ExecStart=__FINALPATH__/my_app`
+# You will replace __NODE_ENV_PATH__ with $ynh_node_load_PATH
+#
+# Or node start the app directly, then you don't need to load the PATH variable
+# `ExecStart=__YNH_NODE__ my_app run`
+# You will replace __YNH_NODE__ with $ynh_node
+#
+#
+# 2 other variables are also available
+# - $nodejs_path: The absolute path to node binaries for the chosen version.
# - $nodejs_version: Just the version number of node for this app. Stored as 'nodejs_version' in settings.yml.
-# And 2 alias stored in variables:
-# - $nodejs_use_version: An old variable, not used anymore. Keep here to not break old apps
-# NB: $PATH will contain the path to node, it has to be propagated to any other shell which needs to use it.
-# That's means it has to be added to any systemd script.
#
# usage: ynh_use_nodejs
#
@@ -43,15 +67,26 @@ SOURCE_SUM=3983fa3f00d4bf85ba8e21f1a590f6e28938093abe0bb950aeea52b1717471fc" > "
ynh_use_nodejs () {
nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version)
- nodejs_use_version="echo \"Deprecated command, should be removed\""
-
# Get the absolute path of this version of node
nodejs_path="$node_version_path/$nodejs_version/bin"
+ # Allow alias to be used into bash script
+ shopt -s expand_aliases
+
+ # Create an alias for the specific version of node and a variable as fallback
+ ynh_node="$nodejs_path/node"
+ alias ynh_node="$ynh_node"
+ # And npm
+ ynh_npm="$nodejs_path/npm"
+ alias ynh_npm="$ynh_npm"
+
# Load the path of this version of node in $PATH
if [[ :$PATH: != *":$nodejs_path"* ]]; then
PATH="$nodejs_path:$PATH"
fi
+ node_PATH="$PATH"
+ # Create an alias to easily load the PATH
+ ynh_node_load_PATH="PATH=$node_PATH"
}
# Install a specific version of nodejs
@@ -64,6 +99,8 @@ ynh_use_nodejs () {
# n (Node version management) uses the PATH variable to store the path of the version of node it is going to use.
# That's how it changes the version
#
+# Refer to ynh_use_nodejs for more information about available commands and variables
+#
# Requires YunoHost version 2.7.12 or higher.
ynh_install_nodejs () {
# Use n, https://github.com/tj/n to manage the nodejs versions
diff --git a/data/helpers.d/php b/data/helpers.d/php
index e8de6d9ff..9b9df64f9 100644
--- a/data/helpers.d/php
+++ b/data/helpers.d/php
@@ -297,7 +297,10 @@ ynh_remove_fpm_config () {
fi
ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf"
- ynh_exec_warn_less ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini"
+ if [ -e $fpm_config_dir/conf.d/20-$app.ini ]
+ then
+ ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini"
+ fi
# If the php version used is not the default version for YunoHost
if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ]
diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql
index f7e9e00fc..78ef4f7ce 100644
--- a/data/helpers.d/postgresql
+++ b/data/helpers.d/postgresql
@@ -1,6 +1,7 @@
#!/bin/bash
PSQL_ROOT_PWD_FILE=/etc/yunohost/psql
+PSQL_VERSION=9.6
# Open a connection as a user
#
@@ -273,46 +274,46 @@ ynh_psql_remove_db() {
}
# Create a master password and set up global settings
+# It also make sure that postgresql is installed and running
# Please always call this script in install and restore scripts
#
# usage: ynh_psql_test_if_first_run
#
# Requires YunoHost version 2.7.13 or higher.
ynh_psql_test_if_first_run() {
- if [ -f "$PSQL_ROOT_PWD_FILE" ]
+
+ # Make sure postgresql is indeed installed
+ dpkg --list | grep -q "ii postgresql-$PSQL_VERSION" || ynh_die "postgresql-$PSQL_VERSION is not installed !?"
+
+ # Check for some weird issue where postgresql could be installed but etc folder would not exist ...
+ [ -e "/etc/postgresql/$PSQL_VERSION" ] || ynh_die "It looks like postgresql was not properly configured ? /etc/postgresql/$PSQL_VERSION is missing ... Could be due to a locale issue, c.f.https://serverfault.com/questions/426989/postgresql-etc-postgresql-doesnt-exist"
+
+ # Make sure postgresql is started and enabled
+ # (N.B. : to check the active state, we check the cluster state because
+ # postgresql could be flagged as active even though the cluster is in
+ # failed state because of how the service is configured..)
+ systemctl is-active postgresql@$PSQL_VERSION-main -q || ynh_systemd_action --service_name=postgresql --action=restart
+ systemctl is-enabled postgresql -q || systemctl enable postgresql
+
+ # If this is the very first time, we define the root password
+ # and configure a few things
+ if [ ! -f "$PSQL_ROOT_PWD_FILE" ]
then
- ynh_print_info --message="PostgreSQL is already installed, no need to create master password"
- return
+ local pg_hba=/etc/postgresql/$PSQL_VERSION/main/pg_hba.conf
+
+ local psql_root_password="$(ynh_string_random)"
+ echo "$psql_root_password" >$PSQL_ROOT_PWD_FILE
+ sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$psql_root_password'" postgres
+
+ # force all user to connect to local databases using hashed passwords
+ # https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html#EXAMPLE-PG-HBA.CONF
+ # Note: we can't use peer since YunoHost create users with nologin
+ # See: https://github.com/YunoHost/yunohost/blob/unstable/data/helpers.d/user
+ ynh_replace_string --match_string="local\(\s*\)all\(\s*\)all\(\s*\)peer" --replace_string="local\1all\2all\3md5" --target_file="$pg_hba"
+
+ # Integrate postgresql service in yunohost
+ yunohost service add postgresql --log "/var/log/postgresql/"
+
+ ynh_systemd_action --service_name=postgresql --action=reload
fi
-
- local psql_root_password="$(ynh_string_random)"
- echo "$psql_root_password" >$PSQL_ROOT_PWD_FILE
-
- if [ -e /etc/postgresql/9.4/ ]
- then
- local pg_hba=/etc/postgresql/9.4/main/pg_hba.conf
- local logfile=/var/log/postgresql/postgresql-9.4-main.log
- elif [ -e /etc/postgresql/9.6/ ]
- then
- local pg_hba=/etc/postgresql/9.6/main/pg_hba.conf
- local logfile=/var/log/postgresql/postgresql-9.6-main.log
- else
- ynh_die "postgresql shoud be 9.4 or 9.6"
- fi
-
- ynh_systemd_action --service_name=postgresql --action=start
-
- sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$psql_root_password'" postgres
-
- # force all user to connect to local databases using hashed passwords
- # https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html#EXAMPLE-PG-HBA.CONF
- # Note: we can't use peer since YunoHost create users with nologin
- # See: https://github.com/YunoHost/yunohost/blob/unstable/data/helpers.d/user
- ynh_replace_string --match_string="local\(\s*\)all\(\s*\)all\(\s*\)peer" --replace_string="local\1all\2all\3md5" --target_file="$pg_hba"
-
- # Advertise service in admin panel
- yunohost service add postgresql --log "$logfile"
-
- systemctl enable postgresql
- ynh_systemd_action --service_name=postgresql --action=reload
}
diff --git a/data/helpers.d/systemd b/data/helpers.d/systemd
index 39db5db53..236781f8b 100644
--- a/data/helpers.d/systemd
+++ b/data/helpers.d/systemd
@@ -3,8 +3,10 @@
# Create a dedicated systemd config
#
# usage: ynh_add_systemd_config [--service=service] [--template=template]
+# usage: ynh_add_systemd_config [--service=service] [--template=template] [--others_var="list of others variables to replace"]
# | arg: -s, --service= - Service name (optionnal, $app by default)
# | arg: -t, --template= - Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template)
+# | arg: -v, --others_var= - List of others variables to replace separated by a space. For example: 'var_1 var_2 ...'
#
# This will use the template ../conf/.service
# to generate a systemd config, by replacing the following keywords
@@ -14,17 +16,23 @@
# __APP__ by $app
# __FINALPATH__ by $final_path
#
+# And dynamic variables (from the last example) :
+# __VAR_1__ by $var_1
+# __VAR_2__ by $var_2
+#
# Requires YunoHost version 2.7.11 or higher.
ynh_add_systemd_config () {
# Declare an array to define the options of this helper.
- local legacy_args=st
- local -A args_array=( [s]=service= [t]=template= )
+ local legacy_args=stv
+ local -A args_array=( [s]=service= [t]=template= [v]=others_var= )
local service
local template
+ local others_var
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
local service="${service:-$app}"
local template="${template:-systemd.service}"
+ others_var="${others_var:-}"
finalsystemdconf="/etc/systemd/system/$service.service"
ynh_backup_if_checksum_is_different --file="$finalsystemdconf"
@@ -38,6 +46,15 @@ ynh_add_systemd_config () {
if [ -n "${app:-}" ]; then
ynh_replace_string --match_string="__APP__" --replace_string="$app" --target_file="$finalsystemdconf"
fi
+
+ # Replace all other variables given as arguments
+ for var_to_replace in $others_var
+ do
+ # ${var_to_replace^^} make the content of the variable on upper-cases
+ # ${!var_to_replace} get the content of the variable named $var_to_replace
+ ynh_replace_string --match_string="__${var_to_replace^^}__" --replace_string="${!var_to_replace}" --target_file="$finalsystemdconf"
+ done
+
ynh_store_file_checksum --file="$finalsystemdconf"
chown root: "$finalsystemdconf"
@@ -128,7 +145,7 @@ ynh_systemd_action() {
if ! systemctl $action $service_name
then
# Show syslog for this service
- ynh_exec_err journalctl --no-pager --lines=$length --unit=$service_name
+ ynh_exec_err journalctl --quiet --no-hostname --no-pager --lines=$length --unit=$service_name
# If a log is specified for this service, show also the content of this log
if [ -e "$log_path" ]
then
@@ -166,7 +183,7 @@ ynh_systemd_action() {
then
ynh_print_warn --message="The service $service_name didn't fully executed the action ${action} before the timeout."
ynh_print_warn --message="Please find here an extract of the end of the log of the service $service_name:"
- ynh_exec_warn journalctl --no-pager --lines=$length --unit=$service_name
+ ynh_exec_warn journalctl --quiet --no-hostname --no-pager --lines=$length --unit=$service_name
if [ -e "$log_path" ]
then
ynh_print_warn --message="\-\-\-"
diff --git a/data/helpers.d/utils b/data/helpers.d/utils
index fb50305ce..9c2f40618 100644
--- a/data/helpers.d/utils
+++ b/data/helpers.d/utils
@@ -35,7 +35,10 @@ ynh_exit_properly () {
ynh_clean_setup # Call the function to do specific cleaning for the app.
fi
- ynh_die # Exit with error status
+ # Exit with error status
+ # We don't call ynh_die basically to avoid unecessary 10-ish
+ # debug lines about parsing args and stuff just to exit 1..
+ exit 1
}
# Exits if an error occurs during the execution of the script.
@@ -141,7 +144,8 @@ ynh_setup_source () {
then # Use the local source file if it is present
cp $local_src $src_filename
else # If not, download the source
- local out=`wget --no-verbose --output-document=$src_filename $src_url 2>&1` || ynh_print_err --message="$out"
+ # Timeout option is here to enforce the timeout on dns query and tcp connect (c.f. man wget)
+ local out=`wget --timeout 900 --no-verbose --output-document=$src_filename $src_url 2>&1` || ynh_print_err --message="$out"
fi
# Check the control sum
diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost
index 236619079..4bd763b70 100755
--- a/data/hooks/conf_regen/01-yunohost
+++ b/data/hooks/conf_regen/01-yunohost
@@ -60,11 +60,35 @@ do_pre_regen() {
mkdir -p $pending_dir/etc/cron.d/
cat > $pending_dir/etc/cron.d/yunohost-diagnosis << EOF
SHELL=/bin/bash
-0 7,19 * * * root : YunoHost Diagnosis; sleep \$((RANDOM\\%600)); yunohost diagnosis run > /dev/null
+0 7,19 * * * root : YunoHost Automatic Diagnosis; sleep \$((RANDOM\\%600)); yunohost diagnosis run --email > /dev/null 2>/dev/null || echo "Running the automatic diagnosis failed miserably"
EOF
}
+do_post_regen() {
+ regen_conf_files=$1
+
+ ######################
+ # Enfore permissions #
+ ######################
+
+ # Certs
+ # We do this with find because there could be a lot of them...
+ chown -R root:ssl-cert /etc/yunohost/certs
+ chmod 750 /etc/yunohost/certs
+ find /etc/yunohost/certs/ -type f -exec chmod 640 {} \;
+ find /etc/yunohost/certs/ -type d -exec chmod 750 {} \;
+
+ # Misc configuration / state files
+ chown root:root $(ls /etc/yunohost/{*.yml,*.yaml,*.json,mysql,psql} 2>/dev/null)
+ chmod 600 $(ls /etc/yunohost/{*.yml,*.yaml,*.json,mysql,psql} 2>/dev/null)
+
+ # Apps folder, custom hooks folder
+ [[ ! -e /etc/yunohost/hooks.d ]] || (chown root /etc/yunohost/hooks.d && chmod 700 /etc/yunohost/hooks.d)
+ [[ ! -e /etc/yunohost/apps ]] || (chown root /etc/yunohost/apps && chmod 700 /etc/yunohost/apps)
+
+}
+
_update_services() {
python2 - << EOF
import yaml
@@ -132,6 +156,7 @@ case "$1" in
do_pre_regen $4
;;
post)
+ do_post_regen $4
;;
init)
do_init_regen
diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd
index 9b2c20138..5fd727a2d 100755
--- a/data/hooks/conf_regen/06-slapd
+++ b/data/hooks/conf_regen/06-slapd
@@ -82,9 +82,6 @@ do_post_regen() {
chown root:openldap /etc/ldap/slapd.conf
chown -R openldap:openldap /etc/ldap/schema/
chown -R openldap:openldap /etc/ldap/slapd.d/
- chown -R root:ssl-cert /etc/yunohost/certs/yunohost.org/
- chmod o-rwx /etc/yunohost/certs/yunohost.org/
- chmod -R g+rx /etc/yunohost/certs/yunohost.org/
# If we changed the systemd ynh-override conf
if echo "$regen_conf_files" | sed 's/,/\n/g' | grep -q "^/etc/systemd/system/slapd.service.d/ynh-override.conf$"
diff --git a/data/hooks/conf_regen/12-metronome b/data/hooks/conf_regen/12-metronome
index 55433e13c..31d11555a 100755
--- a/data/hooks/conf_regen/12-metronome
+++ b/data/hooks/conf_regen/12-metronome
@@ -14,7 +14,6 @@ do_pre_regen() {
# retrieve variables
main_domain=$(cat /etc/yunohost/current_host)
- domain_list=$(yunohost domain list --output-as plain --quiet)
# install main conf file
cat metronome.cfg.lua \
@@ -22,7 +21,7 @@ do_pre_regen() {
> "${metronome_dir}/metronome.cfg.lua"
# add domain conf files
- for domain in $domain_list; do
+ for domain in $YNH_DOMAINS; do
cat domain.tpl.cfg.lua \
| sed "s/{{ domain }}/${domain}/g" \
> "${metronome_conf_dir}/${domain}.cfg.lua"
@@ -33,7 +32,7 @@ do_pre_regen() {
| awk '/^[^\.]+\.[^\.]+.*\.cfg\.lua$/ { print $1 }')
for file in $conf_files; do
domain=${file%.cfg.lua}
- [[ $domain_list =~ $domain ]] \
+ [[ $YNH_DOMAINS =~ $domain ]] \
|| touch "${metronome_conf_dir}/${file}"
done
}
@@ -43,6 +42,9 @@ do_post_regen() {
# retrieve variables
main_domain=$(cat /etc/yunohost/current_host)
+
+ # FIXME : small optimization to do to avoid calling a yunohost command ...
+ # maybe another env variable like YNH_MAIN_DOMAINS idk
domain_list=$(yunohost domain list --exclude-subdomains --output-as plain --quiet)
# create metronome directories for domains
@@ -55,6 +57,9 @@ do_post_regen() {
done
# fix some permissions
+
+ # metronome should be in ssl-cert group to let it access SSL certificates
+ usermod -aG ssl-cert metronome
chown -R metronome: /var/lib/metronome/
chown -R metronome: /etc/metronome/conf.d/
diff --git a/data/hooks/conf_regen/15-nginx b/data/hooks/conf_regen/15-nginx
index 86c7c2438..4924b6f91 100755
--- a/data/hooks/conf_regen/15-nginx
+++ b/data/hooks/conf_regen/15-nginx
@@ -47,14 +47,15 @@ do_pre_regen() {
# retrieve variables
main_domain=$(cat /etc/yunohost/current_host)
- domain_list=$(yunohost domain list --output-as plain --quiet)
# Support different strategy for security configurations
export compatibility="$(yunohost settings get 'security.nginx.compatibility')"
ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc"
+ cert_status=$(yunohost domain cert-status --json)
+
# add domain conf files
- for domain in $domain_list; do
+ for domain in $YNH_DOMAINS; do
domain_conf_dir="${nginx_conf_dir}/${domain}.d"
mkdir -p "$domain_conf_dir"
mail_autoconfig_dir="${pending_dir}/var/www/.well-known/${domain}/autoconfig/mail/"
@@ -62,7 +63,7 @@ do_pre_regen() {
# NGINX server configuration
export domain
- export domain_cert_ca=$(yunohost domain cert-status $domain --json \
+ export domain_cert_ca=$(echo $cert_status \
| jq ".certificates.\"$domain\".CA_type" \
| tr -d '"')
@@ -82,7 +83,7 @@ do_pre_regen() {
| awk '/^[^\.]+\.[^\.]+.*\.conf$/ { print $1 }')
for file in $conf_files; do
domain=${file%.conf}
- [[ $domain_list =~ $domain ]] \
+ [[ $YNH_DOMAINS =~ $domain ]] \
|| touch "${nginx_conf_dir}/${file}"
done
@@ -90,7 +91,7 @@ do_pre_regen() {
autoconfig_files=$(ls -1 /var/www/.well-known/*/autoconfig/mail/config-v1.1.xml 2>/dev/null || true)
for file in $autoconfig_files; do
domain=$(basename $(readlink -f $(dirname $file)/../..))
- [[ $domain_list =~ $domain ]] \
+ [[ $YNH_DOMAINS =~ $domain ]] \
|| (mkdir -p "$(dirname ${pending_dir}/${file})" && touch "${pending_dir}/${file}")
done
@@ -104,16 +105,13 @@ do_post_regen() {
[ -z "$regen_conf_files" ] && exit 0
- # retrieve variables
- domain_list=$(yunohost domain list --output-as plain --quiet)
-
# create NGINX conf directories for domains
- for domain in $domain_list; do
+ for domain in $YNH_DOMAINS; do
mkdir -p "/etc/nginx/conf.d/${domain}.d"
done
# Get rid of legacy lets encrypt snippets
- for domain in $domain_list; do
+ for domain in $YNH_DOMAINS; do
# If the legacy letsencrypt / acme-challenge domain-specific snippet is still there
if [ -e /etc/nginx/conf.d/${domain}.d/000-acmechallenge.conf ]
then
diff --git a/data/hooks/conf_regen/19-postfix b/data/hooks/conf_regen/19-postfix
index 10076b680..68afe4bc9 100755
--- a/data/hooks/conf_regen/19-postfix
+++ b/data/hooks/conf_regen/19-postfix
@@ -20,18 +20,17 @@ do_pre_regen() {
# prepare main.cf conf file
main_domain=$(cat /etc/yunohost/current_host)
- domain_list=$(yunohost domain list --output-as plain --quiet | tr '\n' ' ')
# Support different strategy for security configurations
export compatibility="$(yunohost settings get 'security.postfix.compatibility')"
export main_domain
- export domain_list
+ export domain_list="$YNH_DOMAINS"
ynh_render_template "main.cf" "${postfix_dir}/main.cf"
cat postsrsd \
| sed "s/{{ main_domain }}/${main_domain}/g" \
- | sed "s/{{ domain_list }}/${domain_list}/g" \
+ | sed "s/{{ domain_list }}/${YNH_DOMAINS}/g" \
> "${default_dir}/postsrsd"
# adapt it for IPv4-only hosts
diff --git a/data/hooks/conf_regen/31-rspamd b/data/hooks/conf_regen/31-rspamd
index 26fea4336..861549e27 100755
--- a/data/hooks/conf_regen/31-rspamd
+++ b/data/hooks/conf_regen/31-rspamd
@@ -25,11 +25,8 @@ do_post_regen() {
mkdir -p /etc/dkim
chown _rspamd /etc/dkim
- # retrieve domain list
- domain_list=$(yunohost domain list --output-as plain --quiet)
-
# create DKIM key for domains
- for domain in $domain_list; do
+ for domain in $YNH_DOMAINS; do
domain_key="/etc/dkim/${domain}.mail.key"
[ ! -f "$domain_key" ] && {
# We use a 1024 bit size because nsupdate doesn't seem to be able to
diff --git a/data/hooks/conf_regen/43-dnsmasq b/data/hooks/conf_regen/43-dnsmasq
index 59a1f8a06..8cddec1be 100755
--- a/data/hooks/conf_regen/43-dnsmasq
+++ b/data/hooks/conf_regen/43-dnsmasq
@@ -26,10 +26,9 @@ do_pre_regen() {
ynh_validate_ip4 "$ipv4" || ipv4='127.0.0.1'
ipv6=$(curl -s -6 https://ip6.yunohost.org 2>/dev/null || true)
ynh_validate_ip6 "$ipv6" || ipv6=''
- domain_list=$(yunohost domain list --output-as plain --quiet)
# add domain conf files
- for domain in $domain_list; do
+ for domain in $YNH_DOMAINS; do
cat domain.tpl \
| sed "s/{{ domain }}/${domain}/g" \
| sed "s/{{ ip }}/${ipv4}/g" \
@@ -42,7 +41,7 @@ do_pre_regen() {
conf_files=$(ls -1 /etc/dnsmasq.d \
| awk '/^[^\.]+\.[^\.]+.*$/ { print $1 }')
for domain in $conf_files; do
- [[ $domain_list =~ $domain ]] \
+ [[ $YNH_DOMAINS =~ $domain ]] \
|| touch "${dnsmasq_dir}/${domain}"
done
}
@@ -65,6 +64,11 @@ do_post_regen() {
systemctl restart resolvconf
fi
+ # Some stupid things like rabbitmq-server used by onlyoffice won't work if
+ # the *short* hostname doesn't exists in /etc/hosts -_-
+ short_hostname=$(hostname -s)
+ grep -q "127.0.0.1.*$short_hostname" /etc/hosts || echo -e "\n127.0.0.1\t$short_hostname" >>/etc/hosts
+
[[ -z "$regen_conf_files" ]] \
|| service dnsmasq restart
}
diff --git a/data/hooks/diagnosis/00-basesystem.py b/data/hooks/diagnosis/00-basesystem.py
index 51926924a..ec802c870 100644
--- a/data/hooks/diagnosis/00-basesystem.py
+++ b/data/hooks/diagnosis/00-basesystem.py
@@ -1,9 +1,11 @@
#!/usr/bin/env python
import os
+import json
+import subprocess
from moulinette.utils.process import check_output
-from moulinette.utils.filesystem import read_file
+from moulinette.utils.filesystem import read_file, read_json, write_to_json
from yunohost.diagnosis import Diagnoser
from yunohost.utils.packages import ynh_packages_version
@@ -32,7 +34,7 @@ class BaseSystemDiagnoser(Diagnoser):
# Also possibly the board name
if os.path.exists("/proc/device-tree/model"):
- model = read_file('/proc/device-tree/model').strip()
+ model = read_file('/proc/device-tree/model').strip().replace('\x00', '')
hardware["data"]["model"] = model
hardware["details"] = ["diagnosis_basesystem_hardware_board"]
@@ -74,5 +76,75 @@ class BaseSystemDiagnoser(Diagnoser):
details=ynh_version_details)
+ if self.is_vulnerable_to_meltdown():
+ yield dict(meta={"test": "meltdown"},
+ status="ERROR",
+ summary="diagnosis_security_vulnerable_to_meltdown",
+ details=["diagnosis_security_vulnerable_to_meltdown_details"]
+ )
+
+ def is_vulnerable_to_meltdown(self):
+ # meltdown CVE: https://security-tracker.debian.org/tracker/CVE-2017-5754
+
+ # We use a cache file to avoid re-running the script so many times,
+ # which can be expensive (up to around 5 seconds on ARM)
+ # and make the admin appear to be slow (c.f. the calls to diagnosis
+ # from the webadmin)
+ #
+ # The cache is in /tmp and shall disappear upon reboot
+ # *or* we compare it to dpkg.log modification time
+ # such that it's re-ran if there was package upgrades
+ # (e.g. from yunohost)
+ cache_file = "/tmp/yunohost-meltdown-diagnosis"
+ dpkg_log = "/var/log/dpkg.log"
+ if os.path.exists(cache_file):
+ if not os.path.exists(dpkg_log) or os.path.getmtime(cache_file) > os.path.getmtime(dpkg_log):
+ self.logger_debug("Using cached results for meltdown checker, from %s" % cache_file)
+ return read_json(cache_file)[0]["VULNERABLE"]
+
+ # script taken from https://github.com/speed47/spectre-meltdown-checker
+ # script commit id is store directly in the script
+ SCRIPT_PATH = "/usr/lib/moulinette/yunohost/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh"
+
+ # '--variant 3' corresponds to Meltdown
+ # example output from the script:
+ # [{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}]
+ try:
+ self.logger_debug("Running meltdown vulnerability checker")
+ call = subprocess.Popen("bash %s --batch json --variant 3" %
+ SCRIPT_PATH, shell=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+
+ # TODO / FIXME : here we are ignoring error messages ...
+ # in particular on RPi2 and other hardware, the script complains about
+ # "missing some kernel info (see -v), accuracy might be reduced"
+ # Dunno what to do about that but we probably don't want to harass
+ # users with this warning ...
+ output, err = call.communicate()
+ assert call.returncode in (0, 2, 3), "Return code: %s" % call.returncode
+
+ # If there are multiple lines, sounds like there was some messages
+ # in stdout that are not json >.> ... Try to get the actual json
+ # stuff which should be the last line
+ output = output.strip()
+ if "\n" in output:
+ self.logger_debug("Original meltdown checker output : %s" % output)
+ output = output.split("\n")[-1]
+
+ CVEs = json.loads(output)
+ assert len(CVEs) == 1
+ assert CVEs[0]["NAME"] == "MELTDOWN"
+ except Exception as e:
+ import traceback
+ traceback.print_exc()
+ self.logger_warning("Something wrong happened when trying to diagnose Meltdown vunerability, exception: %s" % e)
+ raise Exception("Command output for failed meltdown check: '%s'" % output)
+
+ self.logger_debug("Writing results from meltdown checker to cache file, %s" % cache_file)
+ write_to_json(cache_file, CVEs)
+ return CVEs[0]["VULNERABLE"]
+
+
def main(args, env, loggers):
return BaseSystemDiagnoser(args, env, loggers).diagnose()
diff --git a/data/hooks/diagnosis/10-ip.py b/data/hooks/diagnosis/10-ip.py
index c0d35278c..b18b4f435 100644
--- a/data/hooks/diagnosis/10-ip.py
+++ b/data/hooks/diagnosis/10-ip.py
@@ -1,5 +1,6 @@
#!/usr/bin/env python
+import re
import os
import random
@@ -10,6 +11,7 @@ from moulinette.utils.filesystem import read_file
from yunohost.diagnosis import Diagnoser
from yunohost.utils.network import get_network_interfaces
+
class IPDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
@@ -72,8 +74,9 @@ class IPDiagnoser(Diagnoser):
ipv6 = self.get_public_ip(6) if can_ping_ipv6 else None
network_interfaces = get_network_interfaces()
+
def get_local_ip(version):
- local_ip = {iface:addr[version].split("/")[0]
+ local_ip = {iface: addr[version].split("/")[0]
for iface, addr in network_interfaces.items() if version in addr}
if not local_ip:
return None
@@ -92,7 +95,7 @@ class IPDiagnoser(Diagnoser):
data={"global": ipv6, "local": get_local_ip("ipv6")},
status="SUCCESS" if ipv6 else "WARNING",
summary="diagnosis_ip_connected_ipv6" if ipv6 else "diagnosis_ip_no_ipv6",
- details=["diagnosis_ip_global", "diagnosis_ip_local"] if ipv6 else None)
+ details=["diagnosis_ip_global", "diagnosis_ip_local"] if ipv6 else ["diagnosis_ip_no_ipv6_tip"])
# TODO / FIXME : add some attempt to detect ISP (using whois ?) ?
@@ -105,9 +108,17 @@ class IPDiagnoser(Diagnoser):
return False
# If we are indeed connected in ipv4 or ipv6, we should find a default route
- routes = check_output("ip -%s route" % protocol).split("\n")
- if not any(r.startswith("default") for r in routes):
- return False
+ routes = check_output("ip -%s route show table all" % protocol).split("\n")
+
+ def is_default_route(r):
+ # Typically the default route starts with "default"
+ # But of course IPv6 is more complex ... e.g. on internet cube there's
+ # no default route but a /3 which acts as a default-like route...
+ # e.g. 2000:/3 dev tun0 ...
+ return r.startswith("default") or (":" in r and re.match(r".*/[0-3]$", r.split()[0]))
+ if not any(is_default_route(r) for r in routes):
+ self.logger_debug("No default route for IPv%s, so assuming there's no IP address for that version" % protocol)
+ return None
# We use the resolver file as a list of well-known, trustable (ie not google ;)) IPs that we can ping
resolver_file = "/usr/share/yunohost/templates/dnsmasq/plain/resolv.dnsmasq.conf"
diff --git a/data/hooks/diagnosis/12-dnsrecords.py b/data/hooks/diagnosis/12-dnsrecords.py
index 53afb2c2d..560127bb0 100644
--- a/data/hooks/diagnosis/12-dnsrecords.py
+++ b/data/hooks/diagnosis/12-dnsrecords.py
@@ -1,13 +1,21 @@
#!/usr/bin/env python
import os
+import re
-from moulinette.utils.filesystem import read_file
+from datetime import datetime, timedelta
+from publicsuffix import PublicSuffixList
+
+from moulinette.utils.process import check_output
from yunohost.utils.network import dig
from yunohost.diagnosis import Diagnoser
from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain
+# We put here domains we know has dyndns provider, but that are not yet
+# registered in the public suffix list
+PENDING_SUFFIX_LIST = ['ynh.fr', 'netlib.re']
+
class DNSRecordsDiagnoser(Diagnoser):
@@ -17,24 +25,22 @@ class DNSRecordsDiagnoser(Diagnoser):
def run(self):
- resolvers = read_file("/etc/resolv.dnsmasq.conf").split("\n")
- ipv4_resolvers = [r.split(" ")[1] for r in resolvers if r.startswith("nameserver") and ":" not in r]
- # FIXME some day ... handle ipv4-only and ipv6-only servers. For now we assume we have at least ipv4
- assert ipv4_resolvers != [], "Uhoh, need at least one IPv4 DNS resolver ..."
-
- self.resolver = ipv4_resolvers[0]
main_domain = _get_maindomain()
all_domains = domain_list()["domains"]
for domain in all_domains:
self.logger_debug("Diagnosing DNS conf for %s" % domain)
- is_subdomain = domain.split(".",1)[1] in all_domains
+ is_subdomain = domain.split(".", 1)[1] in all_domains
for report in self.check_domain(domain, domain == main_domain, is_subdomain=is_subdomain):
yield report
- # FIXME : somewhere, should implement a check for reverse DNS ...
-
- # FIXME / TODO : somewhere, could also implement a check for domain expiring soon
+ # Check if a domain buy by the user will expire soon
+ psl = PublicSuffixList()
+ domains_from_registrar = [psl.get_public_suffix(domain) for domain in all_domains]
+ domains_from_registrar = [domain for domain in domains_from_registrar if "." in domain]
+ domains_from_registrar = set(domains_from_registrar) - set(PENDING_SUFFIX_LIST)
+ for report in self.check_expiration_date(domains_from_registrar):
+ yield report
def check_domain(self, domain, is_main_domain, is_subdomain):
@@ -67,7 +73,6 @@ class DNSRecordsDiagnoser(Diagnoser):
results[id_] = "WRONG"
discrepancies.append(("diagnosis_dns_discrepancy", r))
-
def its_important():
# Every mail DNS records are important for main domain
# For other domain, we only report it as a warning for now...
@@ -128,7 +133,7 @@ class DNSRecordsDiagnoser(Diagnoser):
if r["name"] == "@":
current = {part for part in current if not part.startswith("ip4:") and not part.startswith("ip6:")}
return expected == current
- elif r["type"] == "MX":
+ elif r["type"] == "MX":
# For MX, we want to ignore the priority
expected = r["value"].split()[-1]
current = r["current"].split()[-1]
@@ -136,6 +141,92 @@ class DNSRecordsDiagnoser(Diagnoser):
else:
return r["current"] == r["value"]
+ def check_expiration_date(self, domains):
+ """
+ Alert if expiration date of a domain is soon
+ """
+
+ details = {
+ "not_found": [],
+ "error": [],
+ "warning": [],
+ "success": []
+ }
+
+ for domain in domains:
+ expire_date = self.get_domain_expiration(domain)
+
+ if isinstance(expire_date, str):
+ status_ns, _ = dig(domain, "NS", resolvers="force_external")
+ status_a, _ = dig(domain, "A", resolvers="force_external")
+ if "ok" not in [status_ns, status_a]:
+ details["not_found"].append((
+ "diagnosis_domain_%s_details" % (expire_date),
+ {"domain": domain}))
+ else:
+ self.logger_debug("Dyndns domain: %s" % (domain))
+ continue
+
+ expire_in = expire_date - datetime.now()
+
+ alert_type = "success"
+ if expire_in <= timedelta(15):
+ alert_type = "error"
+ elif expire_in <= timedelta(45):
+ alert_type = "warning"
+
+ args = {
+ "domain": domain,
+ "days": expire_in.days - 1,
+ "expire_date": str(expire_date)
+ }
+ details[alert_type].append(("diagnosis_domain_expires_in", args))
+
+ for alert_type in ["success", "error", "warning", "not_found"]:
+ if details[alert_type]:
+ if alert_type == "not_found":
+ meta = {"test": "domain_not_found"}
+ else:
+ meta = {"test": "domain_expiration"}
+ # Allow to ignore specifically a single domain
+ if len(details[alert_type]) == 1:
+ meta["domain"] = details[alert_type][0][1]["domain"]
+ yield dict(meta=meta,
+ data={},
+ status=alert_type.upper() if alert_type != "not_found" else "WARNING",
+ summary="diagnosis_domain_expiration_" + alert_type,
+ details=details[alert_type])
+
+ def get_domain_expiration(self, domain):
+ """
+ Return the expiration datetime of a domain or None
+ """
+ command = "whois -H %s || echo failed" % (domain)
+ out = check_output(command).strip().split("\n")
+
+ # Reduce output to determine if whois answer is equivalent to NOT FOUND
+ filtered_out = [line for line in out
+ if re.search(r'^[a-zA-Z0-9 ]{4,25}:', line, re.IGNORECASE) and
+ not re.match(r'>>> Last update of whois', line, re.IGNORECASE) and
+ not re.match(r'^NOTICE:', line, re.IGNORECASE) and
+ not re.match(r'^%%', line, re.IGNORECASE) and
+ not re.match(r'"https?:"', line, re.IGNORECASE)]
+
+ # If there is less than 7 lines, it's NOT FOUND response
+ if len(filtered_out) <= 6:
+ return "not_found"
+
+ for line in out:
+ match = re.search(r'Expir.+(\d{4}-\d{2}-\d{2})', line, re.IGNORECASE)
+ if match is not None:
+ return datetime.strptime(match.group(1), '%Y-%m-%d')
+
+ match = re.search(r'Expir.+(\d{2}-\w{3}-\d{4})', line, re.IGNORECASE)
+ if match is not None:
+ return datetime.strptime(match.group(1), '%d-%b-%Y')
+
+ return "expiration_not_found"
+
def main(args, env, loggers):
return DNSRecordsDiagnoser(args, env, loggers).diagnose()
diff --git a/data/hooks/diagnosis/14-ports.py b/data/hooks/diagnosis/14-ports.py
index bd68c60d6..38f6e0089 100644
--- a/data/hooks/diagnosis/14-ports.py
+++ b/data/hooks/diagnosis/14-ports.py
@@ -87,7 +87,7 @@ class PortsDiagnoser(Diagnoser):
# If any AAAA record is set, IPv6 is important...
def ipv6_is_important():
dnsrecords = Diagnoser.get_cached_report("dnsrecords") or {}
- return any(record["data"]["AAAA:@"] in ["OK", "WRONG"] for record in dnsrecords.get("items", []))
+ return any(record["data"].get("AAAA:@") in ["OK", "WRONG"] for record in dnsrecords.get("items", []))
if failed == 4 or ipv6_is_important():
yield dict(meta={"port": port},
diff --git a/data/hooks/diagnosis/24-mail.py b/data/hooks/diagnosis/24-mail.py
index a60b4f0d4..bc159c3b7 100644
--- a/data/hooks/diagnosis/24-mail.py
+++ b/data/hooks/diagnosis/24-mail.py
@@ -2,7 +2,6 @@
import os
import dns.resolver
-import socket
import re
from subprocess import CalledProcessError
@@ -118,15 +117,27 @@ class MailDiagnoser(Diagnoser):
details = ["diagnosis_mail_fcrdns_nok_details",
"diagnosis_mail_fcrdns_nok_alternatives_4"]
- try:
- rdns_domain, _, _ = socket.gethostbyaddr(ip)
- except socket.herror:
+ rev = dns.reversename.from_address(ip)
+ subdomain = str(rev.split(3)[0])
+ query = subdomain
+ if ipversion == 4:
+ query += '.in-addr.arpa'
+ else:
+ query += '.ip6.arpa'
+
+ # Do the DNS Query
+ status, value = dig(query, 'PTR')
+ if status == "nok":
yield dict(meta={"test": "mail_fcrdns", "ipversion": ipversion},
data={"ip": ip, "ehlo_domain": self.ehlo_domain},
status="ERROR",
summary="diagnosis_mail_fcrdns_dns_missing",
details=details)
continue
+
+ rdns_domain = ''
+ if len(value) > 0:
+ rdns_domain = value[0][:-1] if value[0].endswith('.') else value[0]
if rdns_domain != self.ehlo_domain:
details = ["diagnosis_mail_fcrdns_different_from_ehlo_domain_details"] + details
yield dict(meta={"test": "mail_fcrdns", "ipversion": ipversion},
diff --git a/data/hooks/diagnosis/30-services.py b/data/hooks/diagnosis/30-services.py
index 6217d89d3..d0fe50ae9 100644
--- a/data/hooks/diagnosis/30-services.py
+++ b/data/hooks/diagnosis/30-services.py
@@ -21,7 +21,7 @@ class ServicesDiagnoser(Diagnoser):
data={"status": result["status"], "configuration": result["configuration"]})
if result["status"] != "running":
- item["status"] = "ERROR"
+ item["status"] = "ERROR" if result["status"] != "unknown" else "WARNING"
item["summary"] = "diagnosis_services_bad_status"
item["details"] = ["diagnosis_services_bad_status_tip"]
diff --git a/data/hooks/diagnosis/50-systemresources.py b/data/hooks/diagnosis/50-systemresources.py
index 417b88ae7..682fb897f 100644
--- a/data/hooks/diagnosis/50-systemresources.py
+++ b/data/hooks/diagnosis/50-systemresources.py
@@ -45,14 +45,15 @@ class SystemResourcesDiagnoser(Diagnoser):
item = dict(meta={"test": "swap"},
data={"total": human_size(swap.total), "recommended": "512 MiB"})
if swap.total <= 1 * MB:
- item["status"] = "ERROR"
+ item["status"] = "INFO"
item["summary"] = "diagnosis_swap_none"
- elif swap.total <= 512 * MB:
- item["status"] = "WARNING"
+ elif swap.total < 450 * MB:
+ item["status"] = "INFO"
item["summary"] = "diagnosis_swap_notsomuch"
else:
item["status"] = "SUCCESS"
item["summary"] = "diagnosis_swap_ok"
+ item["details"] = ["diagnosis_swap_tip"]
yield item
# FIXME : add a check that swapiness is low if swap is on a sdcard...
@@ -61,40 +62,36 @@ class SystemResourcesDiagnoser(Diagnoser):
# Disks usage
#
- disk_partitions = psutil.disk_partitions()
+ disk_partitions = sorted(psutil.disk_partitions(), key=lambda k: k.mountpoint)
for disk_partition in disk_partitions:
device = disk_partition.device
mountpoint = disk_partition.mountpoint
usage = psutil.disk_usage(mountpoint)
- free_percent = round_(100 - usage.percent)
+ free_percent = 100 - round_(usage.percent)
item = dict(meta={"test": "diskusage", "mountpoint": mountpoint},
- data={"device": device, "total": human_size(usage.total), "free": human_size(usage.free), "free_percent": free_percent})
+ data={"device": device,
+ # N.B.: we do not use usage.total because we want
+ # to take into account the 5% security margin
+ # correctly (c.f. the doc of psutil ...)
+ "total": human_size(usage.used+usage.free),
+ "free": human_size(usage.free),
+ "free_percent": free_percent})
- # Special checks for /boot partition because they sometimes are
- # pretty small and that's kind of okay... (for example on RPi)
- if mountpoint.startswith("/boot"):
- if usage.free < 10 * MB or free_percent < 10:
- item["status"] = "ERROR"
- item["summary"] = "diagnosis_diskusage_verylow"
- elif usage.free < 20 * MB or free_percent < 20:
- item["status"] = "WARNING"
- item["summary"] = "diagnosis_diskusage_low"
- else:
- item["status"] = "SUCCESS"
- item["summary"] = "diagnosis_diskusage_ok"
+ # We have an additional absolute constrain on / and /var because
+ # system partitions are critical, having them full may prevent
+ # upgrades etc...
+ if free_percent < 2.5 or (mountpoint in ["/", "/var"] and usage.free < 1 * GB):
+ item["status"] = "ERROR"
+ item["summary"] = "diagnosis_diskusage_verylow"
+ elif free_percent < 5 or (mountpoint in ["/", "/var"] and usage.free < 2 * GB):
+ item["status"] = "WARNING"
+ item["summary"] = "diagnosis_diskusage_low"
else:
- if usage.free < 1 * GB or free_percent < 5:
- item["status"] = "ERROR"
- item["summary"] = "diagnosis_diskusage_verylow"
- elif usage.free < 2 * GB or free_percent < 10:
- item["status"] = "WARNING"
- item["summary"] = "diagnosis_diskusage_low"
- else:
- item["status"] = "SUCCESS"
- item["summary"] = "diagnosis_diskusage_ok"
+ item["status"] = "SUCCESS"
+ item["summary"] = "diagnosis_diskusage_ok"
yield item
diff --git a/data/hooks/diagnosis/90-security.py b/data/hooks/diagnosis/90-security.py
deleted file mode 100644
index d281042b0..000000000
--- a/data/hooks/diagnosis/90-security.py
+++ /dev/null
@@ -1,98 +0,0 @@
-#!/usr/bin/env python
-
-import os
-import json
-import subprocess
-
-from yunohost.diagnosis import Diagnoser
-from moulinette.utils.filesystem import read_json, write_to_json
-
-
-class SecurityDiagnoser(Diagnoser):
-
- id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
- cache_duration = 3600
- dependencies = []
-
- def run(self):
-
- "CVE-2017-5754"
-
- if self.is_vulnerable_to_meltdown():
- yield dict(meta={"test": "meltdown"},
- status="ERROR",
- summary="diagnosis_security_vulnerable_to_meltdown",
- details=["diagnosis_security_vulnerable_to_meltdown_details"]
- )
- else:
- yield dict(meta={},
- status="SUCCESS",
- summary="diagnosis_security_all_good"
- )
-
-
- def is_vulnerable_to_meltdown(self):
- # meltdown CVE: https://security-tracker.debian.org/tracker/CVE-2017-5754
-
- # We use a cache file to avoid re-running the script so many times,
- # which can be expensive (up to around 5 seconds on ARM)
- # and make the admin appear to be slow (c.f. the calls to diagnosis
- # from the webadmin)
- #
- # The cache is in /tmp and shall disappear upon reboot
- # *or* we compare it to dpkg.log modification time
- # such that it's re-ran if there was package upgrades
- # (e.g. from yunohost)
- cache_file = "/tmp/yunohost-meltdown-diagnosis"
- dpkg_log = "/var/log/dpkg.log"
- if os.path.exists(cache_file):
- if not os.path.exists(dpkg_log) or os.path.getmtime(cache_file) > os.path.getmtime(dpkg_log):
- self.logger_debug("Using cached results for meltdown checker, from %s" % cache_file)
- return read_json(cache_file)[0]["VULNERABLE"]
-
- # script taken from https://github.com/speed47/spectre-meltdown-checker
- # script commit id is store directly in the script
- SCRIPT_PATH = "/usr/lib/moulinette/yunohost/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh"
-
- # '--variant 3' corresponds to Meltdown
- # example output from the script:
- # [{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}]
- try:
- self.logger_debug("Running meltdown vulnerability checker")
- call = subprocess.Popen("bash %s --batch json --variant 3" %
- SCRIPT_PATH, shell=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
-
- # TODO / FIXME : here we are ignoring error messages ...
- # in particular on RPi2 and other hardware, the script complains about
- # "missing some kernel info (see -v), accuracy might be reduced"
- # Dunno what to do about that but we probably don't want to harass
- # users with this warning ...
- output, err = call.communicate()
- assert call.returncode in (0, 2, 3), "Return code: %s" % call.returncode
-
- # If there are multiple lines, sounds like there was some messages
- # in stdout that are not json >.> ... Try to get the actual json
- # stuff which should be the last line
- output = output.strip()
- if "\n" in output:
- self.logger_debug("Original meltdown checker output : %s" % output)
- output = output.split("\n")[-1]
-
- CVEs = json.loads(output)
- assert len(CVEs) == 1
- assert CVEs[0]["NAME"] == "MELTDOWN"
- except Exception as e:
- import traceback
- traceback.print_exc()
- self.logger_warning("Something wrong happened when trying to diagnose Meltdown vunerability, exception: %s" % e)
- raise Exception("Command output for failed meltdown check: '%s'" % output)
-
- self.logger_debug("Writing results from meltdown checker to cache file, %s" % cache_file)
- write_to_json(cache_file, CVEs)
- return CVEs[0]["VULNERABLE"]
-
-
-def main(args, env, loggers):
- return SecurityDiagnoser(args, env, loggers).diagnose()
diff --git a/data/templates/nginx/plain/yunohost_admin.conf.inc b/data/templates/nginx/plain/yunohost_admin.conf.inc
index 2ab72293d..8b81ab932 100644
--- a/data/templates/nginx/plain/yunohost_admin.conf.inc
+++ b/data/templates/nginx/plain/yunohost_admin.conf.inc
@@ -6,6 +6,9 @@ location /yunohost/admin/ {
default_type text/html;
index index.html;
+ more_set_headers "Content-Security-Policy: upgrade-insecure-requests; default-src 'self'; connect-src 'self' https://raw.githubusercontent.com https://paste.yunohost.org wss://$host; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-eval'; object-src 'none';";
+ more_set_headers "Content-Security-Policy-Report-Only:";
+
# Short cache on handlebars templates
location ~* \.(?:ms)$ {
expires 5m;
diff --git a/data/templates/nginx/security.conf.inc b/data/templates/nginx/security.conf.inc
index ff3d2ee99..dea0f49db 100644
--- a/data/templates/nginx/security.conf.inc
+++ b/data/templates/nginx/security.conf.inc
@@ -22,7 +22,7 @@ ssl_prefer_server_ciphers off;
# https://wiki.mozilla.org/Security/Guidelines/Web_Security
# https://observatory.mozilla.org/
more_set_headers "Content-Security-Policy : upgrade-insecure-requests";
-more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' 'unsafe-eval'";
+more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' 'unsafe-eval' ";
more_set_headers "X-Content-Type-Options : nosniff";
more_set_headers "X-XSS-Protection : 1; mode=block";
more_set_headers "X-Download-Options : noopen";
diff --git a/data/templates/nginx/yunohost_admin.conf b/data/templates/nginx/yunohost_admin.conf
index 3df838c4a..d13dbfe90 100644
--- a/data/templates/nginx/yunohost_admin.conf
+++ b/data/templates/nginx/yunohost_admin.conf
@@ -22,7 +22,6 @@ server {
more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload";
more_set_headers "Referrer-Policy : 'same-origin'";
- more_set_headers "Content-Security-Policy : upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'";
location / {
return 302 https://$http_host/yunohost/admin;
diff --git a/debian/changelog b/debian/changelog
index b3f607c9f..83ae9c3d4 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,126 @@
+yunohost (3.8.4.3) stable; urgency=low
+
+ - [fix] Workaround for the sury pinning issues when installing dependencies
+ - [i18n] Translations updated for Catalan, French, Occitan
+
+ Thanks to all contributors <3 ! (Aleks, clecle226, Kay0u, ppr, Quenti)
+
+ -- Kay0u Wed, 20 May 2020 18:41:49 +0000
+
+yunohost (3.8.4.2) testing; urgency=low
+
+ - [enh] During failed upgrades: Only mention packages that couldn't be upgraded (26fcfed7)
+ - [enh] Also run dpkg --audit to check if dpkg is in a broken state (09d8500f, 97199d19)
+ - [enh] Improve logs readability (c6f18496, 9cbd368d, 5850bf61, 413778d2, 5c8c07b8, f73c34bf, 94ea8265)
+ - [enh] Crash early about apps already installed when attempting to restore (f9e4c96c)
+ - [fix] Add the damn short hostname to /etc/hosts automagically (c.f. rabbitmq-server) (e67dc791)
+ - [fix] Don't miserably crash if doveadm fails to run (c9b22138)
+ - [fix] Diagnosis: Try to not have weird warnings if no diagnosis ran yet... (65c87d55)
+ - [fix] Diagnosis: Change logic of --email to avoid sending empty mail if some issues are found but ignored (4cd4938e)
+ - [enh] Diagnosis/services: Report the service status as warning/unknown if service type is oneshot and status exited (dd09758f, 1cd7ffea)
+ - [fix] Rework ynh_psql_test_if_first_run ([#993](https://github.com/yunohost/yunohost/pull/993))
+ - [tests] Tests for args parsing ([#989](https://github.com/yunohost/yunohost/pull/989), 108a3ca4)
+
+ Thanks to all contributors <3 ! (Bram, Kayou)
+
+ -- Alexandre Aubin Tue, 19 May 2020 20:08:47 +0200
+
+yunohost (3.8.4.1) testing; urgency=low
+
+ - [mod] Tweak diagnosis threshold for swap warning (429df8c4)
+ - [fix] Make sure we have a list for log_list + make sure item is in list before using .remove()... (afbeb145, 43facfd5)
+ - [fix] Sometimes tree-model has a weird \x00 which breaks yunopaste (c346f5f1)
+
+ -- Alexandre Aubin Mon, 11 May 2020 00:50:34 +0200
+
+yunohost (3.8.4) testing; urgency=low
+
+ - [fix] Restoration of custom hooks / missing restore hooks (#927)
+ - [enh] Real CSP headers for the webadmin (#961)
+ - [enh] Simplify / optimize reading version of yunohost packages... (#968)
+ - [fix] handle new auto restart of ldap in moulinette (#975)
+ - [enh] service.py cleanup + add tests for services (#979, #986)
+ - [fix] Enforce permissions for stuff in /etc/yunohost/ (#963)
+ - [mod] Remove security diagnosis category for now, Move meltdown check to base system (a799740a)
+ - [mod] Change warning/errors about swap as info instead ... add a tip about the fact that having swap on SD or SSD is dangerous (23147161)
+ - [enh] Improve auto diagnosis cron UX, add a --human-readable option to diagnosis_show() (aecbb14a)
+ - [enh] Rely on new diagnosis for letsencrypt elligibility (#985)
+ - [i18n] Translations updated for Catalan, Esperanto, French, Spanish
+
+ Thanks to all contributors <3 ! (amirale qt, autra, Bram, clecle226, I. Hernández, Kay0u, xaloc33)
+
+ -- Alexandre Aubin Sat, 09 May 2020 21:20:00 +0200
+
+yunohost (3.8.3) testing; urgency=low
+
+ - [fix] Remove dot in reverse DNS check
+ - [fix] Upgrade of multi-instance apps was broken (#976)
+ - [fix] Check was broken if an apps with no domain setting was installed (#978)
+ - [enh] Add a timeout to wget (#972)
+ - [fix] ynh_get_ram: Enforce choosing --free or --total (#972)
+ - [fix] Simplify / improve robustness of backup list
+ - [enh] Make nodejs helpers easier to use (#939)
+ - [fix] Misc tweak for disk usage diagnosis, some values were inconsistent / bad UX / ...
+ - [enh] Assert slapd is running to avoid miserably crashing with weird ldap errors
+ - [enh] Try to show smarter / more useful logs by filtering irrelevant lines like set +x etc
+ - Technical tweaks for metronome 3.14.0 support
+ - Misc improvements for tests and linters
+
+ Thanks to all contributors <3 ! (Bram, Kay0u, Maniack C., ljf, Maranda)
+
+ -- Alexandre Aubin Thu, 07 Apr 2020 04:00:00 +0000
+
+yunohost (3.8.2.2) testing; urgency=low
+
+ Aleks broke everything /again/ *.*
+
+ -- Alexandre Aubin Thu, 30 Apr 2020 18:05:00 +0000
+
+yunohost (3.8.2.1) testing; urgency=low
+
+ - [fix] Make sure DNS queries are dong using absolute names to avoid stupid issues
+ - [fix] More reliable way to fetch PTR record / reverse DNS
+ - [fix] Propagate IPv6 default route check to ip diagnoser code as well
+
+ Thanks to ljf for the tests and fixes !
+
+ -- Alexandre Aubin Thu, 30 Apr 2020 17:30:00 +0000
+
+yunohost (3.8.2) testing; urgency=low
+
+ ### Diagnosis
+
+ - [fix] Some DNS queries triggered false negatives about CNAME/A record and email blacklisting (#943)
+ - [enh] Add a check about domain expiration (#944)
+ - [enh] Dirty hack to automatically find custom SSH port and diagnose it instead of 22 (b78d722)
+ - [enh] Add a tip / explanation when IPv6 ain't working / available (426d938)
+ - [fix] Small false-negative about not having IPv6 when it's actually working (822c731)
+
+ ### Helpers
+
+ - [fix] When setting up a new db, corresponding user should be declared as owner (#813)
+ - [enh] Add dynamic variables to systemd helper (#937)
+ - [enh] Clean helpers (#947)
+ - [fix] getopts behaved in weird way when fed empty parameters (#948)
+ - [fix] Use ynh_port_available in ynh_find_port (#957)
+
+ ### Others
+
+ - [enh] Setup all XMPP components for each "parent" domains (#916)
+ - [fix] Previous change in Postfix ciphers broke TLS (#949)
+ - [fix] Update ACME snippet detection following previous change (#950)
+ - [fix] Trying to install apps with unpatchable legacy helpers was breaking stuff (#954)
+ - [fix] Patch usage of old 'yunohost tools diagnosis' (#954)
+ - [enh] Misc optimizations to speed up regen-conf and other things (#958)
+ - [enh] When sharing logs, also anonymize folder name containing %2e instead of dot (b392efd)
+ - [enh] Keep track of yunohost version a backup was made from (54cc684)
+ - [fix] Re-add 'app fetchlist', 'app list -i', 'app list' filter for backward compatibility... (69938c3)
+ - [i18n] Improve translations for Catalan, German, French, Esperanto, Spanish, Greek, Nepali, Occitan
+
+ Thanks to all contributors <3 ! (Bram, C. Wehrli, Kay0u, Maniack C., Quentí, Zeik0s, amirale qt, ljf, pitchum, tituspijean, xaloc33, Éric G.)
+
+ -- Alexandre Aubin Wed, 29 Apr 2020 23:15:00 +0000
+
yunohost (3.8.1.1) testing; urgency=low
- [fix] Stupid issue about path in debian/install ...
@@ -74,6 +197,12 @@ yunohost (3.8.0) testing; urgency=low
-- Kay0u Thu, 09 Apr 2020 19:59:18 +0000
+yunohost (3.7.1.3) stable; urgency=low
+
+ - [fix] Fix the hotfix about trailing slash, it was breaking access to app installed on domain root..
+
+ -- Alexandre Aubin Thu, 28 Apr 2020 19:00:00 +0000
+
yunohost (3.7.1.2) stable; urgency=low
- [fix] Be more robust against some situation where some archives are corrupted
diff --git a/debian/control b/debian/control
index 5bcd78491..fdbf4c98b 100644
--- a/debian/control
+++ b/debian/control
@@ -13,8 +13,8 @@ Architecture: all
Depends: ${python:Depends}, ${misc:Depends}
, moulinette (>= 3.7), ssowat (>= 3.7)
, python-psutil, python-requests, python-dnspython, python-openssl
- , python-apt, python-miniupnpc, python-dbus, python-jinja2
- , python-toml
+ , python-miniupnpc, python-dbus, python-jinja2
+ , python-toml, python-packaging
, apt, apt-transport-https
, nginx, nginx-extras (>=1.6.2)
, php-fpm, php-ldap, php-intl
@@ -27,9 +27,9 @@ Depends: ${python:Depends}, ${misc:Depends}
, dovecot-core, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved, dovecot-antispam
, rspamd (>= 1.6.0), opendkim-tools, postsrsd, procmail, mailutils
, redis-server
- , metronome
+ , metronome (>=3.14.0)
, git, curl, wget, cron, unzip, jq
- , lsb-release, haveged, fake-hwclock, equivs, lsof
+ , lsb-release, haveged, fake-hwclock, equivs, lsof, whois, python-publicsuffix
Recommends: yunohost-admin
, ntp, inetutils-ping | iputils-ping
, bash-completion, rsyslog
diff --git a/doc/helper_doc_template.html b/doc/helper_doc_template.html
index 92611c737..f96a0190e 100644
--- a/doc/helper_doc_template.html
+++ b/doc/helper_doc_template.html
@@ -2,6 +2,8 @@
App helpers
+Doc auto-generated by this script on {{data.date}} (Yunohost version {{data.version}})
+
{% for category, helpers in data.helpers %}
{{ category }}
@@ -70,7 +72,7 @@
{% endif %}
- Dude, show me the code !
+ Dude, show me the code !
@@ -81,9 +83,6 @@
{% endfor %}
{% endfor %}
-Generated by this script on {{data.date}} (Yunohost version {{data.version}})
-
-