Merge branch 'stretch-unstable' of github.com:eauchat/yunohost into stretch-unstable

This commit is contained in:
chateau 2018-12-16 22:29:43 +01:00
commit 06522b1fa8
43 changed files with 1192 additions and 984 deletions

3
.gitignore vendored
View file

@ -32,3 +32,6 @@ pip-log.txt
# moulinette lib
src/yunohost/locales
# Test
src/yunohost/tests/apps

View file

@ -5,7 +5,7 @@ ip=$(hostname --all-ip-address)
# Fetch SSH fingerprints
i=0
for key in /etc/ssh/ssh_host_*_key.pub ; do
for key in $(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key.pub 2> /dev/null) ; do
output=$(ssh-keygen -l -f $key)
fingerprint[$i]=" - $(echo $output | cut -d' ' -f2) $(echo $output| cut -d' ' -f4)"
i=$(($i + 1))

View file

@ -558,6 +558,10 @@ app:
full: --no-remove-on-failure
help: Debug option to avoid removing the app on a failed installation
action: store_true
-f:
full: --force
help: Do not ask confirmation if the app is not safe to use (low quality, experimental or 3rd party)
action: store_true
### app_remove() TODO: Write help
remove:

View file

@ -10,12 +10,13 @@ CAN_BIND=${CAN_BIND:-1}
#
# If DEST is ended by a slash it complete this path with the basename of SRC.
#
# usage: ynh_backup src [dest [is_big [arg]]]
# usage: ynh_backup src [dest [is_big [not_mandatory [arg]]]]
# | arg: src - file or directory to bind or symlink or copy. it shouldn't be in
# the backup dir.
# | arg: dest - destination file or directory inside the
# backup dir
# | arg: is_big - 1 to indicate data are big (mail, video, image ...)
# | arg: not_mandatory - 1 to indicate that if the file is missing, the backup can ignore it.
# | arg: arg - Deprecated arg
#
# example:
@ -46,6 +47,7 @@ ynh_backup() {
local SRC_PATH="$1"
local DEST_PATH="${2:-}"
local IS_BIG="${3:-0}"
local NOT_MANDATORY="${4:-0}"
BACKUP_CORE_ONLY=${BACKUP_CORE_ONLY:-0}
# If backing up core only (used by ynh_backup_before_upgrade),
@ -60,15 +62,18 @@ ynh_backup() {
# ==============================================================================
# Be sure the source path is not empty
[[ -e "${SRC_PATH}" ]] || {
echo "!!! Source path '${SRC_PATH}' does not exist !!!" >&2
# This is a temporary fix for fail2ban config files missing after the migration to stretch.
if echo "${SRC_PATH}" | grep --quiet "/etc/fail2ban"
if [ "$NOT_MANDATORY" == "0" ]
then
touch "${SRC_PATH}"
echo "The missing file will be replaced by a dummy one for the backup !!!" >&2
# This is a temporary fix for fail2ban config files missing after the migration to stretch.
if echo "${SRC_PATH}" | grep --quiet "/etc/fail2ban"
then
touch "${SRC_PATH}"
echo "The missing file will be replaced by a dummy one for the backup !!!" >&2
else
return 1
fi
else
return 1
return 0
fi
}
@ -176,12 +181,13 @@ with open(sys.argv[1], 'r') as backup_file:
# Use the registered path in backup_list by ynh_backup to restore the file at
# the good place.
#
# usage: ynh_restore_file ORIGIN_PATH [ DEST_PATH ]
# usage: ynh_restore_file ORIGIN_PATH [ DEST_PATH [NOT_MANDATORY]]
# | arg: ORIGIN_PATH - Path where was located the file or the directory before
# to be backuped or relative path to $YNH_CWD where it is located in the backup archive
# | arg: DEST_PATH - Path where restore the file or the dir, if unspecified,
# the destination will be ORIGIN_PATH or if the ORIGIN_PATH doesn't exist in
# the archive, the destination will be searched into backup.csv
# | arg: NOT_MANDATORY - 1 to indicate that if the file is missing, the restore process can ignore it.
#
# If DEST_PATH already exists and is lighter than 500 Mo, a backup will be made in
# /home/yunohost.conf/backup/. Otherwise, the existing file is removed.
@ -201,10 +207,16 @@ ynh_restore_file () {
local ARCHIVE_PATH="$YNH_CWD${ORIGIN_PATH}"
# Default value for DEST_PATH = /$ORIGIN_PATH
local DEST_PATH="${2:-$ORIGIN_PATH}"
local NOT_MANDATORY="${3:-0}"
# If ARCHIVE_PATH doesn't exist, search for a corresponding path in CSV
if [ ! -d "$ARCHIVE_PATH" ] && [ ! -f "$ARCHIVE_PATH" ] && [ ! -L "$ARCHIVE_PATH" ]; then
ARCHIVE_PATH="$YNH_BACKUP_DIR/$(_get_archive_path \"$ORIGIN_PATH\")"
if [ "$NOT_MANDATORY" == "0" ]
then
ARCHIVE_PATH="$YNH_BACKUP_DIR/$(_get_archive_path \"$ORIGIN_PATH\")"
else
return 0
fi
fi
# Move the old directory if it already exists

View file

@ -272,6 +272,7 @@ ynh_local_curl () {
ynh_render_template() {
local template_path=$1
local output_path=$2
mkdir -p "$(dirname $output_path)"
# Taken from https://stackoverflow.com/a/35009576
python2.7 -c 'import os, sys, jinja2; sys.stdout.write(
jinja2.Template(sys.stdin.read()

View file

@ -2,28 +2,53 @@
set -e
. /usr/share/yunohost/helpers.d/utils
do_pre_regen() {
pending_dir=$1
pending_dir=$1
cd /usr/share/yunohost/templates/ssh
# If the (legacy) 'from_script' flag is here,
# we won't touch anything in the ssh config.
[[ ! -f /etc/yunohost/from_script ]] || return 0
# only overwrite SSH configuration on an ISO installation
if [[ ! -f /etc/yunohost/from_script ]]; then
# do not listen to IPv6 if unavailable
[[ -f /proc/net/if_inet6 ]] \
|| sed -i "s/ListenAddress ::/#ListenAddress ::/g" sshd_config
cd /usr/share/yunohost/templates/ssh
# do not listen to IPv6 if unavailable
[[ -f /proc/net/if_inet6 ]] && ipv6_enabled=true || ipv6_enabled=false
install -D -m 644 sshd_config "${pending_dir}/etc/ssh/sshd_config"
fi
# Support legacy setting (this setting might be disabled by a user during a migration)
ssh_keys=$(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key 2>/dev/null)
if [[ "$(yunohost settings get 'service.ssh.allow_deprecated_dsa_hostkey')" == "True" ]]; then
ssh_keys="$ssh_keys $(ls /etc/ssh/ssh_host_dsa_key 2>/dev/null)"
fi
ssh_keys=$(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key 2>/dev/null)
# Support legacy setting (this setting might be disabled by a user during a migration)
if [[ "$(yunohost settings get 'service.ssh.allow_deprecated_dsa_hostkey')" == "True" ]]; then
ssh_keys="$ssh_keys $(ls /etc/ssh/ssh_host_dsa_key 2>/dev/null)"
fi
export ssh_keys
export ipv6_enabled
ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config"
}
do_post_regen() {
regen_conf_files=$1
regen_conf_files=$1
if [[ ! -f /etc/yunohost/from_script ]]; then
[[ -z "$regen_conf_files" ]] \
|| sudo service ssh restart
fi
# If the (legacy) 'from_script' flag is here,
# we won't touch anything in the ssh config.
[[ ! -f /etc/yunohost/from_script ]] || return 0
# If no file changed, there's nothing to do
[[ -n "$regen_conf_files" ]] || return 0
# Enforce permissions for /etc/ssh/sshd_config
chown root:root "/etc/ssh/sshd_config"
chmod 644 "/etc/ssh/sshd_config"
systemctl restart ssh
}
FORCE=${2:-0}

View file

@ -1,96 +1,78 @@
# Package generated configuration file
# See the sshd_config(5) manpage for details
# This configuration has been automatically generated
# by YunoHost
# What ports, IPs and protocols we listen for
Port 22
# Use these options to restrict which interfaces/protocols sshd will bind to
ListenAddress ::
ListenAddress 0.0.0.0
Protocol 2
# HostKeys for protocol version 2
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_dsa_key
#Privilege Separation is turned on for security
UsePrivilegeSeparation yes
Port 22
# Lifetime and size of ephemeral version 1 server key
KeyRegenerationInterval 3600
ServerKeyBits 768
{% if ipv6_enabled == "true" %}ListenAddress ::{% endif %}
ListenAddress 0.0.0.0
# Logging
{% for key in ssh_keys.split() %}
HostKey {{ key }}{% endfor %}
# ##############################################
# Stuff recommended by Mozilla "modern" compat'
# https://infosec.mozilla.org/guidelines/openssh
# ##############################################
# Keys, ciphers and MACS
KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com
# Use kernel sandbox mechanisms where possible in unprivileged processes
UsePrivilegeSeparation sandbox
# LogLevel VERBOSE logs user's key fingerprint on login.
# Needed to have a clear audit track of which key was using to log in.
SyslogFacility AUTH
LogLevel INFO
LogLevel VERBOSE
# #######################
# Authentication settings
# #######################
# Comment from Mozilla about the motivation behind disabling root login
#
# Root login is not allowed for auditing reasons. This is because it's difficult to track which process belongs to which root user:
#
# On Linux, user sessions are tracking using a kernel-side session id, however, this session id is not recorded by OpenSSH.
# Additionally, only tools such as systemd and auditd record the process session id.
# On other OSes, the user session id is not necessarily recorded at all kernel-side.
# Using regular users in combination with /bin/su or /usr/bin/sudo ensure a clear audit track.
# Authentication:
LoginGraceTime 120
PermitRootLogin no
StrictModes yes
RSAAuthentication yes
PubkeyAuthentication yes
#AuthorizedKeysFile %h/.ssh/authorized_keys
# Don't read the user's ~/.rhosts and ~/.shosts files
IgnoreRhosts yes
# For this to work you will also need host keys in /etc/ssh_known_hosts
RhostsRSAAuthentication no
# similar for protocol version 2
HostbasedAuthentication no
# Uncomment if you don't trust ~/.ssh/known_hosts for RhostsRSAAuthentication
#IgnoreUserKnownHosts yes
# To enable empty passwords, change to yes (NOT RECOMMENDED)
PermitEmptyPasswords no
# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication no
# Change to no to disable tunnelled clear text passwords
#PasswordAuthentication yes
# Kerberos options
#KerberosAuthentication no
#KerberosGetAFSToken no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes
X11Forwarding yes
X11DisplayOffset 10
PrintMotd no
PrintLastLog yes
TCPKeepAlive yes
#UseLogin no
# keep ssh sessions fresh
ClientAliveInterval 60
#MaxStartups 10:30:60
Banner /etc/issue.net
# Allow client to pass locale environment variables
AcceptEnv LANG LC_*
Subsystem sftp internal-sftp
# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication. Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
UsePAM yes
# Change to no to disable tunnelled clear text passwords
# (i.e. everybody will need to authenticate using ssh keys)
#PasswordAuthentication yes
# Post-login stuff
Banner /etc/issue.net
PrintMotd no
PrintLastLog yes
ClientAliveInterval 60
AcceptEnv LANG LC_*
# SFTP stuff
Subsystem sftp internal-sftp
Match User sftpusers
ForceCommand internal-sftp
ChrootDirectory /home/%u
AllowTcpForwarding no
GatewayPorts no
X11Forwarding no
# root login is allowed on local networks
# It's meant to be a backup solution in case LDAP is down and
# user admin can't be used...
# If the server is a VPS, it's expected that the owner of the
# server has access to a web console through which to log in.
Match Address 192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,169.254.0.0/16,fe80::/10,fd00::/8
PermitRootLogin yes

18
debian/changelog vendored
View file

@ -1,17 +1,19 @@
yunohost (3.3.3) stable; urgency=low
* [fix] ynh_wait_dpkg_free displaying a warning despite everything being okay (#593)
* [fix] Quotes for recommended CAA DNS record (#596)
* [fix] Manual migration and disclaimer behaviors (#594)
* [fix] Explicit root password change each time admin password is changed
-- Alexandre Aubin <alex.aubin@mailoo.org> Sun, 09 Dec 2018 20:58:00 +0000
yunohost (3.3.2) stable; urgency=low
* [fix] Regen nginx conf to be sure it integrates OCSP Stapling (#588)
* [fix] Broken new settings and options to control passwords checks / constrains (#589)
* [fix] Log dyndns update only if we really update something (#591)
-- Alexandre Aubin <alex.aubin@mailoo.org> Sun, 02 Dev 2018 17:23:00 +0000
yunohost (3.3.2) stable; urgency=low
* [fix] Log dyndns update only if we really update something (#591)
* [fix] Broken new settings and options to control passwords checks / constrains (#589)
-- Alexandre Aubin <alex.aubin@mailoo.org> Sun, 02 Dev 2018 17:17:00 +0000
-- Alexandre Aubin <alex.aubin@mailoo.org> Sun, 02 Dec 2018 17:23:00 +0000
yunohost (3.3.1) stable; urgency=low

View file

@ -1,4 +1,5 @@
{
"aborting": "Aborting.",
"action_invalid": "Invalid action '{action:s}'",
"admin_password": "Administration password",
"admin_password_change_failed": "Unable to change password",
@ -132,6 +133,9 @@
"certmanager_no_cert_file": "Unable to read certificate file for domain {domain:s} (file: {file:s})",
"certmanager_self_ca_conf_file_not_found": "Configuration file not found for self-signing authority (file: {file:s})",
"certmanager_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority (file: {file:s})",
"confirm_app_install_warning": "Warning: this application may work but is not well-integrated in YunoHost. Some features such as single sign-on and backup/restore might not be available. Install anyway? [{answers:s}] ",
"confirm_app_install_danger": "WARNING! This application is still experimental (if not explicitly not working) and it is likely to break your system! You should probably NOT install it unless you know what you are doing. Are you willing to take that risk? [{answers:s}] ",
"confirm_app_install_thirdparty": "WARNING! Installing 3rd party applications may compromise the integrity and security of your system. You should probably NOT install it unless you know what you are doing. Are you willing to take that risk? [{answers:s}] ",
"custom_app_url_required": "You must provide a URL to upgrade your custom app {app:s}",
"custom_appslist_name_required": "You must provide a name for your custom app list",
"diagnosis_debian_version_error": "Can't retrieve the Debian version: {error}",
@ -194,7 +198,10 @@
"global_settings_setting_example_enum": "Example enum option",
"global_settings_setting_example_int": "Example int option",
"global_settings_setting_example_string": "Example string option",
"global_settings_setting_security_password_admin_strength": "Admin password strength",
"global_settings_setting_security_password_user_strength": "User password strength",
"global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key:s}', discarding it and save it in /etc/yunohost/unkown_settings.json",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration",
"global_settings_unknown_type": "Unexpected situation, the setting {setting:s} appears to have the type {unknown_type:s} but it's not a type supported by the system.",
"good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",
"good_practices_about_user_password": "You are now about to define a new user password. The password should be at least 8 characters - though it is good practice to use longer password (i.e. a passphrase) and/or to use various kind of characters (uppercase, lowercase, digits and special characters).",
@ -211,7 +218,7 @@
"log_category_404": "The log category '{category}' does not exist",
"log_link_to_log": "Full log of this operation: '<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>'",
"log_help_to_get_log": "To view the log of the operation '{desc}', use the command 'yunohost log display {name}'",
"log_link_to_failed_log": "The operation '{desc}' has failed ! To get help, please <a href=\"#/tools/logs/{name}\">provide the full log of this operation</a>",
"log_link_to_failed_log": "The operation '{desc}' has failed ! To get help, please <a href=\"#/tools/logs/{name}\">provide the full log of this operation by clicking here</a>",
"log_help_to_get_failed_log": "The operation '{desc}' has failed ! To get help, please share the full log of this operation using the command 'yunohost log display {name} --share'",
"log_category_404": "The log category '{category}' does not exist",
"log_does_exists": "There is not operation log with the name '{log}', use 'yunohost log list to see all available operation logs'",
@ -274,6 +281,8 @@
"migration_description_0004_php5_to_php7_pools": "Reconfigure the PHP pools to use PHP 7 instead of 5",
"migration_description_0005_postgresql_9p4_to_9p6": "Migrate databases from postgresql 9.4 to 9.6",
"migration_description_0006_sync_admin_and_root_passwords": "Synchronize admin and root passwords",
"migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Let the SSH configuration be managed by YunoHost (step 1, automatic)",
"migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Let the SSH configuration be managed by YunoHost (step 2, manual)",
"migration_0003_backward_impossible": "The stretch migration cannot be reverted.",
"migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.",
"migration_0003_patching_sources_list": "Patching the sources.lists ...",
@ -291,7 +300,14 @@
"migration_0005_postgresql_96_not_installed": "Postgresql 9.4 has been found to be installed, but not postgresql 9.6 !? Something weird might have happened on your system :( ...",
"migration_0005_not_enough_space": "Not enough space is available in {path} to run the migration right now :(.",
"migration_0006_disclaimer": "Yunohost now expects admin and root passwords to be synchronized. By running this migration, your root password is going to be replaced by the admin password.",
"migration_0006_done": "Your root password have been replaced by your admin password.",
"migration_0007_cancelled": "YunoHost has failed to improve the way your SSH conf is managed.",
"migration_0007_cannot_restart": "SSH can't be restarted after trying to cancel migration number 6.",
"migration_0008_general_disclaimer": "To improve the security of your server, it is recommended to let YunoHost manage the SSH configuration. Your current SSH configuration differs from the recommended configuration. If you let YunoHost reconfigure it, the way you connect to your server through SSH will change in the following way:",
"migration_0008_port": " - you will have to connect using port 22 instead of your current custom SSH port. Feel free to reconfigure it ;",
"migration_0008_root": " - you will not be able to connect as root through SSH. Instead you should use the admin user ;",
"migration_0008_dsa": " - the DSA key will be disabled. Hence, you might need to invalidate a spooky warning from your SSH client, and recheck the fingerprint of your server ;",
"migration_0008_warning": "If you understand those warnings and agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.",
"migration_0008_no_warning": "No major risk has been indentified about overriding your SSH configuration - but we can't be absolutely sure ;) ! If you agree to let YunoHost override your current configuration, run the migration. Otherwise, you can also skip the migration - though it is not recommended.",
"migrations_backward": "Migrating backward.",
"migrations_bad_value_for_target": "Invalid number for target argument, available migrations numbers are 0 or {}",
"migrations_cant_reach_migration_file": "Can't access migrations files at path %s",
@ -305,6 +321,7 @@
"migrations_show_currently_running_migration": "Running migration {number} {name}...",
"migrations_show_last_migration": "Last ran migration is {}",
"migrations_skip_migration": "Skipping migration {number} {name}...",
"migrations_success": "Successfully ran migration {number} {name}!",
"migrations_to_be_ran_manually": "Migration {number} {name} has to be ran manually. Please go to Tools > Migrations on the webadmin, or run `yunohost tools migrations migrate`.",
"migrations_need_to_accept_disclaimer": "To run the migration {number} {name}, your must accept the following disclaimer:\n---\n{disclaimer}\n---\nIf you accept to run the migration, please re-run the command with the option --accept-disclaimer.",
"monitor_disabled": "The server monitoring has been disabled",
@ -375,6 +392,7 @@
"restore_running_hooks": "Running restoration hooks...",
"restore_system_part_failed": "Unable to restore the '{part:s}' system part",
"root_password_desynchronized": "The admin password has been changed, but YunoHost was unable to propagate this on the root password !",
"root_password_replaced_by_admin_password": "Your root password have been replaced by your admin password.",
"server_shutdown": "The server will shutdown",
"server_shutdown_confirm": "The server will shutdown immediatly, are you sure? [{answers:s}]",
"server_reboot": "The server will reboot",
@ -392,7 +410,7 @@
"service_conf_file_remove_failed": "Unable to remove the configuration file '{conf}'",
"service_conf_file_removed": "The configuration file '{conf}' has been removed",
"service_conf_file_updated": "The configuration file '{conf}' has been updated",
"service_conf_new_managed_file": "The configuration file '{conf}' is now managed by the service {service}.",
"service_conf_now_managed_by_yunohost": "The configuration file '{conf}' is now managed by YunoHost.",
"service_conf_up_to_date": "The configuration is already up-to-date for service '{service}'",
"service_conf_updated": "The configuration has been updated for service '{service}'",
"service_conf_would_be_updated": "The configuration would have been updated for service '{service}'",

View file

@ -30,15 +30,15 @@ import yaml
import time
import re
import urlparse
import errno
import subprocess
import glob
import pwd
import grp
from collections import OrderedDict
from datetime import datetime
from moulinette import msignals, m18n, msettings
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_json
@ -80,6 +80,12 @@ def app_listlists():
# Get the list
appslist_list = _read_appslist_list()
# Convert 'lastUpdate' timestamp to datetime
for name, infos in appslist_list.items():
if infos["lastUpdate"] is None:
infos["lastUpdate"] = 0
infos["lastUpdate"] = datetime.utcfromtimestamp(infos["lastUpdate"])
return appslist_list
@ -118,14 +124,12 @@ def app_fetchlist(url=None, name=None):
appslists_to_be_fetched = [name]
operation_logger.success()
else:
raise MoulinetteError(errno.EINVAL,
m18n.n('custom_appslist_name_required'))
raise YunohostError('custom_appslist_name_required')
# If a name is given, look for an appslist with that name and fetch it
elif name is not None:
if name not in appslists.keys():
raise MoulinetteError(errno.EINVAL,
m18n.n('appslist_unknown', appslist=name))
raise YunohostError('appslist_unknown', appslist=name)
else:
appslists_to_be_fetched = [name]
@ -133,7 +137,7 @@ def app_fetchlist(url=None, name=None):
else:
appslists_to_be_fetched = appslists.keys()
import requests # lazy loading this module for performance reasons
import requests # lazy loading this module for performance reasons
# Fetch all appslists to be fetched
for name in appslists_to_be_fetched:
@ -168,7 +172,7 @@ def app_fetchlist(url=None, name=None):
appslist = appslist_request.text
try:
json.loads(appslist)
except ValueError, e:
except ValueError as e:
logger.error(m18n.n('appslist_retrieve_bad_format',
appslist=name))
continue
@ -179,9 +183,7 @@ def app_fetchlist(url=None, name=None):
with open(list_file, "w") as f:
f.write(appslist)
except Exception as e:
raise MoulinetteError(errno.EIO,
"Error while writing appslist %s: %s" %
(name, str(e)))
raise YunohostError("Error while writing appslist %s: %s" % (name, str(e)), raw_msg=True)
now = int(time.time())
appslists[name]["lastUpdate"] = now
@ -205,7 +207,7 @@ def app_removelist(operation_logger, name):
# Make sure we know this appslist
if name not in appslists.keys():
raise MoulinetteError(errno.ENOENT, m18n.n('appslist_unknown', appslist=name))
raise YunohostError('appslist_unknown', appslist=name)
operation_logger.start()
@ -336,8 +338,7 @@ def app_info(app, show_status=False, raw=False):
"""
if not _is_installed(app):
raise MoulinetteError(errno.EINVAL,
m18n.n('app_not_installed', app=app))
raise YunohostError('app_not_installed', app=app)
app_setting_path = APPS_SETTING_PATH + app
@ -395,8 +396,7 @@ def app_map(app=None, raw=False, user=None):
if app is not None:
if not _is_installed(app):
raise MoulinetteError(errno.EINVAL,
m18n.n('app_not_installed', app=app))
raise YunohostError('app_not_installed', app=app)
apps = [app, ]
else:
apps = os.listdir(APPS_SETTING_PATH)
@ -448,11 +448,10 @@ def app_change_url(operation_logger, auth, app, domain, path):
installed = _is_installed(app)
if not installed:
raise MoulinetteError(errno.ENOPKG,
m18n.n('app_not_installed', app=app))
raise YunohostError('app_not_installed', app=app)
if not os.path.exists(os.path.join(APPS_SETTING_PATH, app, "scripts", "change_url")):
raise MoulinetteError(errno.EINVAL, m18n.n("app_change_no_change_url_script", app_name=app))
raise YunohostError("app_change_no_change_url_script", app_name=app)
old_domain = app_setting(app, "domain")
old_path = app_setting(app, "path")
@ -464,7 +463,7 @@ def app_change_url(operation_logger, auth, app, domain, path):
path = normalize_url_path(path)
if (domain, path) == (old_domain, old_path):
raise MoulinetteError(errno.EINVAL, m18n.n("app_change_url_identical_domains", domain=domain, path=path))
raise YunohostError("app_change_url_identical_domains", domain=domain, path=path)
# WARNING / FIXME : checkurl will modify the settings
# (this is a non intuitive behavior that should be changed)
@ -540,10 +539,10 @@ def app_change_url(operation_logger, auth, app, domain, path):
stderr=subprocess.STDOUT,
shell=True).rstrip()
raise MoulinetteError(errno.EINVAL, m18n.n("app_change_url_failed_nginx_reload", nginx_errors=nginx_errors))
raise YunohostError("app_change_url_failed_nginx_reload", nginx_errors=nginx_errors)
logger.success(m18n.n("app_change_url_success",
app=app, domain=domain, path=path))
app=app, domain=domain, path=path))
hook_callback('post_app_change_url', args=args_list, env=env_dict)
@ -565,8 +564,8 @@ def app_upgrade(auth, app=[], url=None, file=None):
try:
app_list()
except MoulinetteError:
raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade'))
except YunohostError:
raise YunohostError('app_no_upgrade')
upgraded_apps = []
@ -586,8 +585,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
logger.info(m18n.n('app_upgrade_app_name', app=app_instance_name))
installed = _is_installed(app_instance_name)
if not installed:
raise MoulinetteError(errno.ENOPKG,
m18n.n('app_not_installed', app=app_instance_name))
raise YunohostError('app_not_installed', app=app_instance_name)
if app_instance_name in upgraded_apps:
continue
@ -677,7 +675,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
operation_logger.success()
if not upgraded_apps:
raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade'))
raise YunohostError('app_no_upgrade')
app_ssowatconf(auth)
@ -689,7 +687,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
@is_unit_operation()
def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on_failure=False):
def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on_failure=False, force=False):
"""
Install apps
@ -698,12 +696,11 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
label -- Custom name for the app
args -- Serialize arguments for app installation
no_remove_on_failure -- Debug option to avoid removing the app on a failed installation
force -- Do not ask for confirmation when installing experimental / low-quality apps
"""
from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback
from yunohost.log import OperationLogger
# Fetch or extract sources
try:
os.listdir(INSTALL_TMP)
@ -718,17 +715,46 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
},
}
if app in app_list(raw=True) or ('@' in app) or ('http://' in app) or ('https://' in app):
def confirm_install(confirm):
# Ignore if there's nothing for confirm (good quality app), if --force is used
# or if request on the API (confirm already implemented on the API side)
if confirm is None or force or msettings.get('interface') == 'api':
return
answer = msignals.prompt(m18n.n('confirm_app_install_' + confirm,
answers='Y/N'))
if answer.upper() != "Y":
raise YunohostError("aborting")
raw_app_list = app_list(raw=True)
if app in raw_app_list or ('@' in app) or ('http://' in app) or ('https://' in app):
if app in raw_app_list:
state = raw_app_list[app].get("state", "notworking")
level = raw_app_list[app].get("level", None)
confirm = "danger"
if state in ["working", "validated"]:
if isinstance(level, int) and level >= 3:
confirm = None
elif isinstance(level, int) and level > 0:
confirm = "warning"
else:
confirm = "thirdparty"
confirm_install(confirm)
manifest, extracted_app_folder = _fetch_app_from_git(app)
elif os.path.exists(app):
confirm_install("thirdparty")
manifest, extracted_app_folder = _extract_app_from_file(app)
else:
raise MoulinetteError(errno.EINVAL, m18n.n('app_unknown'))
raise YunohostError('app_unknown')
status['remote'] = manifest.get('remote', {})
# Check ID
if 'id' not in manifest or '__' in manifest['id']:
raise MoulinetteError(errno.EINVAL, m18n.n('app_id_invalid'))
raise YunohostError('app_id_invalid')
app_id = manifest['id']
@ -739,8 +765,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
instance_number = _installed_instance_number(app_id, last=True) + 1
if instance_number > 1:
if 'multi_instance' not in manifest or not is_true(manifest['multi_instance']):
raise MoulinetteError(errno.EEXIST,
m18n.n('app_already_installed', app=app_id))
raise YunohostError('app_already_installed', app=app_id)
# Change app_id to the forked app id
app_instance_name = app_id + '__' + str(instance_number)
@ -761,7 +786,7 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(instance_number)
# Start register change on system
operation_logger.extra.update({'env':env_dict})
operation_logger.extra.update({'env': env_dict})
operation_logger.related_to = [s for s in operation_logger.related_to if s[0] != "app"]
operation_logger.related_to.append(("app", app_id))
operation_logger.start()
@ -819,8 +844,8 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
# Execute remove script
operation_logger_remove = OperationLogger('remove_on_failed_install',
[('app', app_instance_name)],
env=env_dict_remove)
[('app', app_instance_name)],
env=env_dict_remove)
operation_logger_remove.start()
remove_retcode = hook_exec(
@ -843,9 +868,9 @@ def app_install(operation_logger, auth, app, label=None, args=None, no_remove_on
if install_retcode == -1:
msg = m18n.n('operation_interrupted') + " " + error_msg
raise MoulinetteError(errno.EINTR, msg)
raise YunohostError(msg, raw_msg=True)
msg = error_msg
raise MoulinetteError(errno.EIO, msg)
raise YunohostError(msg, raw_msg=True)
# Clean hooks and add new ones
hook_remove(app_instance_name)
@ -881,8 +906,7 @@ def app_remove(operation_logger, auth, app):
"""
from yunohost.hook import hook_exec, hook_remove, hook_callback
if not _is_installed(app):
raise MoulinetteError(errno.EINVAL,
m18n.n('app_not_installed', app=app))
raise YunohostError('app_not_installed', app=app)
operation_logger.start()
@ -948,7 +972,6 @@ def app_addaccess(auth, apps, users=[]):
for app in apps:
app_settings = _get_app_settings(app)
if not app_settings:
continue
@ -961,7 +984,7 @@ def app_addaccess(auth, apps, users=[]):
# Start register change on system
related_to = [('app', app)]
operation_logger= OperationLogger('app_addaccess', related_to)
operation_logger = OperationLogger('app_addaccess', related_to)
operation_logger.start()
allowed_users = set()
@ -972,7 +995,7 @@ def app_addaccess(auth, apps, users=[]):
if allowed_user not in allowed_users:
try:
user_info(auth, allowed_user)
except MoulinetteError:
except YunohostError:
logger.warning(m18n.n('user_unknown', user=allowed_user))
continue
allowed_users.add(allowed_user)
@ -1024,7 +1047,7 @@ def app_removeaccess(auth, apps, users=[]):
# Start register change on system
related_to = [('app', app)]
operation_logger= OperationLogger('app_removeaccess', related_to)
operation_logger = OperationLogger('app_removeaccess', related_to)
operation_logger.start()
if remove_all:
@ -1038,7 +1061,7 @@ def app_removeaccess(auth, apps, users=[]):
if allowed_user not in users:
allowed_users.add(allowed_user)
operation_logger.related_to += [ ('user', x) for x in allowed_users ]
operation_logger.related_to += [('user', x) for x in allowed_users]
operation_logger.flush()
new_users = ','.join(allowed_users)
app_setting(app, 'allowed_users', new_users)
@ -1073,7 +1096,7 @@ def app_clearaccess(auth, apps):
# Start register change on system
related_to = [('app', app)]
operation_logger= OperationLogger('app_clearaccess', related_to)
operation_logger = OperationLogger('app_clearaccess', related_to)
operation_logger.start()
if 'mode' in app_settings:
@ -1130,23 +1153,19 @@ def app_makedefault(operation_logger, auth, app, domain=None):
if domain is None:
domain = app_domain
operation_logger.related_to.append(('domain',domain))
operation_logger.related_to.append(('domain', domain))
elif domain not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
raise YunohostError('domain_unknown')
operation_logger.start()
if '/' in app_map(raw=True)[domain]:
raise MoulinetteError(errno.EEXIST,
m18n.n('app_make_default_location_already_used',
app=app, domain=app_domain,
other_app=app_map(raw=True)[domain]["/"]["id"]))
raise YunohostError('app_make_default_location_already_used', app=app, domain=app_domain, other_app=app_map(raw=True)[domain]["/"]["id"])
try:
with open('/etc/ssowat/conf.json.persistent') as json_conf:
ssowat_conf = json.loads(str(json_conf.read()))
except ValueError as e:
raise MoulinetteError(errno.EINVAL,
m18n.n('ssowat_persistent_conf_read_error', error=e.strerror))
raise YunohostError('ssowat_persistent_conf_read_error', error=e.strerror)
except IOError:
ssowat_conf = {}
@ -1159,8 +1178,7 @@ def app_makedefault(operation_logger, auth, app, domain=None):
with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
json.dump(ssowat_conf, f, sort_keys=True, indent=4)
except IOError as e:
raise MoulinetteError(errno.EPERM,
m18n.n('ssowat_persistent_conf_write_error', error=e.strerror))
raise YunohostError('ssowat_persistent_conf_write_error', error=e.strerror)
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
@ -1212,8 +1230,7 @@ def app_checkport(port):
if tools_port_available(port):
logger.success(m18n.n('port_available', port=int(port)))
else:
raise MoulinetteError(errno.EINVAL,
m18n.n('port_unavailable', port=int(port)))
raise YunohostError('port_unavailable', port=int(port))
def app_register_url(auth, app, domain, path):
@ -1228,7 +1245,7 @@ def app_register_url(auth, app, domain, path):
# This line can't be moved on top of file, otherwise it creates an infinite
# loop of import with tools.py...
from domain import _get_conflicting_apps, _normalize_domain_path
from .domain import _get_conflicting_apps, _normalize_domain_path
domain, path = _normalize_domain_path(domain, path)
@ -1240,8 +1257,7 @@ def app_register_url(auth, app, domain, path):
if installed:
settings = _get_app_settings(app)
if "path" in settings.keys() and "domain" in settings.keys():
raise MoulinetteError(errno.EINVAL,
m18n.n('app_already_installed_cant_change_url'))
raise YunohostError('app_already_installed_cant_change_url')
# Check the url is available
conflicts = _get_conflicting_apps(auth, domain, path)
@ -1255,7 +1271,7 @@ def app_register_url(auth, app, domain, path):
app_label=app_label,
))
raise MoulinetteError(errno.EINVAL, m18n.n('app_location_unavailable', apps="\n".join(apps)))
raise YunohostError('app_location_unavailable', apps="\n".join(apps))
app_setting(app, 'domain', value=domain)
app_setting(app, 'path', value=path)
@ -1293,7 +1309,7 @@ def app_checkurl(auth, url, app=None):
apps_map = app_map(raw=True)
if domain not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
raise YunohostError('domain_unknown')
if domain in apps_map:
# Loop through apps
@ -1303,14 +1319,10 @@ def app_checkurl(auth, url, app=None):
installed = True
continue
if path == p:
raise MoulinetteError(errno.EINVAL,
m18n.n('app_location_already_used',
app=a["id"], path=path))
raise YunohostError('app_location_already_used', app=a["id"], path=path)
# can't install "/a/b/" if "/a/" exists
elif path.startswith(p) or p.startswith(path):
raise MoulinetteError(errno.EPERM,
m18n.n('app_location_install_failed',
other_path=p, other_app=a['id']))
raise YunohostError('app_location_install_failed', other_path=p, other_app=a['id'])
if app is not None and not installed:
app_setting(app, 'domain', value=domain)
@ -1342,10 +1354,10 @@ def app_initdb(user, password=None, db=None, sql=None):
mysql_root_pwd = open('/etc/yunohost/mysql').read().rstrip()
mysql_command = 'mysql -u root -p%s -e "CREATE DATABASE %s ; GRANT ALL PRIVILEGES ON %s.* TO \'%s\'@localhost IDENTIFIED BY \'%s\';"' % (mysql_root_pwd, db, db, user, password)
if os.system(mysql_command) != 0:
raise MoulinetteError(errno.EIO, m18n.n('mysql_db_creation_failed'))
raise YunohostError('mysql_db_creation_failed')
if sql is not None:
if os.system('mysql -u %s -p%s %s < %s' % (user, password, db, sql)) != 0:
raise MoulinetteError(errno.EIO, m18n.n('mysql_db_init_failed'))
raise YunohostError('mysql_db_init_failed')
if return_pwd:
return password
@ -1451,8 +1463,7 @@ def app_ssowatconf(auth):
def app_change_label(auth, app, new_label):
installed = _is_installed(app)
if not installed:
raise MoulinetteError(errno.ENOPKG,
m18n.n('app_not_installed', app=app))
raise YunohostError('app_not_installed', app=app)
app_setting(app, "label", value=new_label)
@ -1488,7 +1499,7 @@ def app_action_run(app, action, args=None):
actions = {x["id"]: x for x in actions}
if action not in actions:
raise MoulinetteError(errno.EINVAL, "action '%s' not available for app '%s', available actions are: %s" % (action, app, ", ".join(actions.keys())))
raise YunohostError("action '%s' not available for app '%s', available actions are: %s" % (action, app, ", ".join(actions.keys())), raw_msg=True)
action_declaration = actions[action]
@ -1526,7 +1537,7 @@ def app_action_run(app, action, args=None):
)
if retcode not in action_declaration.get("accepted_return_codes", [0]):
raise MoulinetteError(retcode, "Error while executing action '%s' of app '%s': return code %s" % (action, app, retcode))
raise YunohostError("Error while executing action '%s' of app '%s': return code %s" % (action, app, retcode), raw_msg=True)
os.remove(path)
@ -1585,10 +1596,10 @@ def app_config_show_panel(app):
parsed_values[key] = value
return_code = hook_exec(config_script,
args=["show"],
env=env,
stdout_callback=parse_stdout,
)
args=["show"],
env=env,
stdout_callback=parse_stdout,
)
if return_code != 0:
raise Exception("script/config show return value code: %s (considered as an error)", return_code)
@ -1633,8 +1644,7 @@ def app_config_apply(app, args):
installed = _is_installed(app)
if not installed:
raise MoulinetteError(errno.ENOPKG,
m18n.n('app_not_installed', app=app))
raise YunohostError('app_not_installed', app=app)
config_panel = os.path.join(APPS_SETTING_PATH, app, 'config_panel.json')
config_script = os.path.join(APPS_SETTING_PATH, app, 'scripts', 'config')
@ -1673,9 +1683,9 @@ def app_config_apply(app, args):
logger.warning("Ignore key '%s' from arguments because it is not in the config", key)
return_code = hook_exec(config_script,
args=["apply"],
env=env,
)
args=["apply"],
env=env,
)
if return_code != 0:
raise Exception("'script/config apply' return value code: %s (considered as an error)", return_code)
@ -1692,8 +1702,7 @@ def _get_app_settings(app_id):
"""
if not _is_installed(app_id):
raise MoulinetteError(errno.EINVAL,
m18n.n('app_not_installed', app=app_id))
raise YunohostError('app_not_installed', app=app_id)
try:
with open(os.path.join(
APPS_SETTING_PATH, app_id, 'settings.yml')) as f:
@ -1731,7 +1740,7 @@ def _get_app_status(app_id, format_date=False):
"""
app_setting_path = APPS_SETTING_PATH + app_id
if not os.path.isdir(app_setting_path):
raise MoulinetteError(errno.EINVAL, m18n.n('app_unknown'))
raise YunohostError('app_unknown')
status = {}
try:
@ -1755,8 +1764,7 @@ def _get_app_status(app_id, format_date=False):
if not v:
status[f] = '-'
else:
status[f] = time.strftime(m18n.n('format_datetime_short'),
time.gmtime(v))
status[f] = datetime.utcfromtimestamp(v)
return status
@ -1797,7 +1805,7 @@ def _extract_app_from_file(path, remove=False):
extract_result = 1
if extract_result != 0:
raise MoulinetteError(errno.EINVAL, m18n.n('app_extraction_failed'))
raise YunohostError('app_extraction_failed')
try:
extracted_app_folder = APP_TMP_FOLDER
@ -1808,10 +1816,9 @@ def _extract_app_from_file(path, remove=False):
manifest = json.loads(str(json_manifest.read()))
manifest['lastUpdate'] = int(time.time())
except IOError:
raise MoulinetteError(errno.EIO, m18n.n('app_install_files_invalid'))
raise YunohostError('app_install_files_invalid')
except ValueError as e:
raise MoulinetteError(errno.EINVAL,
m18n.n('app_manifest_invalid', error=e.strerror))
raise YunohostError('app_manifest_invalid', error=e.strerror)
logger.debug(m18n.n('done'))
@ -1879,8 +1886,7 @@ def _fetch_app_from_git(app):
'wget', '-qO', app_tmp_archive, tarball_url])
except subprocess.CalledProcessError:
logger.exception('unable to download %s', tarball_url)
raise MoulinetteError(errno.EIO,
m18n.n('app_sources_fetch_failed'))
raise YunohostError('app_sources_fetch_failed')
else:
manifest, extracted_app_folder = _extract_app_from_file(
app_tmp_archive, remove=True)
@ -1903,11 +1909,9 @@ def _fetch_app_from_git(app):
with open(extracted_app_folder + '/manifest.json') as f:
manifest = json.loads(str(f.read()))
except subprocess.CalledProcessError:
raise MoulinetteError(errno.EIO,
m18n.n('app_sources_fetch_failed'))
raise YunohostError('app_sources_fetch_failed')
except ValueError as e:
raise MoulinetteError(errno.EIO,
m18n.n('app_manifest_invalid', error=e.strerror))
raise YunohostError('app_manifest_invalid', error=e.strerror)
else:
logger.debug(m18n.n('done'))
@ -1927,11 +1931,10 @@ def _fetch_app_from_git(app):
app_info['manifest']['lastUpdate'] = app_info['lastUpdate']
manifest = app_info['manifest']
else:
raise MoulinetteError(errno.EINVAL, m18n.n('app_unknown'))
raise YunohostError('app_unknown')
if 'git' not in app_info:
raise MoulinetteError(errno.EINVAL,
m18n.n('app_unsupported_remote_type'))
raise YunohostError('app_unsupported_remote_type')
url = app_info['git']['url']
if 'github.com' in url:
@ -1943,8 +1946,7 @@ def _fetch_app_from_git(app):
'wget', '-qO', app_tmp_archive, tarball_url])
except subprocess.CalledProcessError:
logger.exception('unable to download %s', tarball_url)
raise MoulinetteError(errno.EIO,
m18n.n('app_sources_fetch_failed'))
raise YunohostError('app_sources_fetch_failed')
else:
manifest, extracted_app_folder = _extract_app_from_file(
app_tmp_archive, remove=True)
@ -1960,11 +1962,9 @@ def _fetch_app_from_git(app):
with open(extracted_app_folder + '/manifest.json') as f:
manifest = json.loads(str(f.read()))
except subprocess.CalledProcessError:
raise MoulinetteError(errno.EIO,
m18n.n('app_sources_fetch_failed'))
raise YunohostError('app_sources_fetch_failed')
except ValueError as e:
raise MoulinetteError(errno.EIO,
m18n.n('app_manifest_invalid', error=e.strerror))
raise YunohostError('app_manifest_invalid', error=e.strerror)
else:
logger.debug(m18n.n('done'))
@ -2083,7 +2083,7 @@ def _check_manifest_requirements(manifest, app_instance_name):
yunohost_req = requirements.get('yunohost', None)
if (not yunohost_req or
not packages.SpecifierSet(yunohost_req) & '>= 2.3.6'):
raise MoulinetteError(errno.EINVAL, '{0}{1}'.format(
raise YunohostError('{0}{1}'.format(
m18n.g('colon', m18n.n('app_incompatible'), app=app_instance_name),
m18n.n('app_package_need_update', app=app_instance_name)))
elif not requirements:
@ -2096,18 +2096,15 @@ def _check_manifest_requirements(manifest, app_instance_name):
versions = packages.get_installed_version(
*requirements.keys(), strict=True, as_dict=True)
except packages.PackageException as e:
raise MoulinetteError(errno.EINVAL,
m18n.n('app_requirements_failed',
error=str(e), app=app_instance_name))
raise YunohostError('app_requirements_failed', error=str(e), app=app_instance_name)
# Iterate over requirements
for pkgname, spec in requirements.items():
version = versions[pkgname]
if version not in packages.SpecifierSet(spec):
raise MoulinetteError(
errno.EINVAL, m18n.n('app_requirements_unmeet',
pkgname=pkgname, version=version,
spec=spec, app=app_instance_name))
raise YunohostError('app_requirements_unmeet',
pkgname=pkgname, version=version,
spec=spec, app=app_instance_name)
def _parse_args_from_manifest(manifest, action, args={}, auth=None):
@ -2215,7 +2212,6 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None):
elif arg_type == 'password':
msignals.display(m18n.n('good_practices_about_user_password'))
try:
input_string = msignals.prompt(ask_string, is_password)
except NotImplementedError:
@ -2231,36 +2227,27 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None):
# Validate argument value
if (arg_value is None or arg_value == '') \
and not arg.get('optional', False):
raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_required', name=arg_name))
raise YunohostError('app_argument_required', name=arg_name)
elif arg_value is None:
args_dict[arg_name] = ''
continue
# Validate argument choice
if arg_choices and arg_value not in arg_choices:
raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_choice_invalid',
name=arg_name, choices=', '.join(arg_choices)))
raise YunohostError('app_argument_choice_invalid', name=arg_name, choices=', '.join(arg_choices))
# Validate argument type
if arg_type == 'domain':
if arg_value not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_invalid',
name=arg_name, error=m18n.n('domain_unknown')))
raise YunohostError('app_argument_invalid', name=arg_name, error=m18n.n('domain_unknown'))
elif arg_type == 'user':
try:
user_info(auth, arg_value)
except MoulinetteError as e:
raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_invalid',
name=arg_name, error=e.strerror))
except YunohostError as e:
raise YunohostError('app_argument_invalid', name=arg_name, error=e.strerror)
elif arg_type == 'app':
if not _is_installed(arg_value):
raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_invalid',
name=arg_name, error=m18n.n('app_unknown')))
raise YunohostError('app_argument_invalid', name=arg_name, error=m18n.n('app_unknown'))
elif arg_type == 'boolean':
if isinstance(arg_value, bool):
arg_value = 1 if arg_value else 0
@ -2270,9 +2257,7 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None):
elif str(arg_value).lower() in ["0", "no", "n"]:
arg_value = 0
else:
raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_choice_invalid',
name=arg_name, choices='yes, no, y, n, 1, 0'))
raise YunohostError('app_argument_choice_invalid', name=arg_name, choices='yes, no, y, n, 1, 0')
elif arg_type == 'password':
from yunohost.utils.password import assert_password_is_strong_enough
assert_password_is_strong_enough('user', arg_value)
@ -2306,7 +2291,7 @@ def _parse_action_args_in_yunohost_format(args, action_args, auth=None):
app_label=app_label,
))
raise MoulinetteError(errno.EINVAL, m18n.n('app_location_unavailable', apps="\n".join(apps)))
raise YunohostError('app_location_unavailable', apps="\n".join(apps))
# (We save this normalized path so that the install script have a
# standard path format to deal with no matter what the user inputted)
@ -2426,7 +2411,7 @@ def _install_appslist_fetch_cron():
with open(cron_job_file, "w") as f:
f.write('\n'.join(cron_job))
_set_permissions(cron_job_file, "root", "root", 0755)
_set_permissions(cron_job_file, "root", "root", 0o755)
# FIXME - Duplicate from certificate.py, should be moved into a common helper
@ -2456,8 +2441,7 @@ def _read_appslist_list():
try:
appslists = json.loads(appslists_json)
except ValueError:
raise MoulinetteError(errno.EBADR,
m18n.n('appslist_corrupted_json', filename=APPSLISTS_JSON))
raise YunohostError('appslist_corrupted_json', filename=APPSLISTS_JSON)
return appslists
@ -2472,9 +2456,8 @@ def _write_appslist_list(appslist_lists):
with open(APPSLISTS_JSON, "w") as f:
json.dump(appslist_lists, f)
except Exception as e:
raise MoulinetteError(errno.EIO,
"Error while writing list of appslist %s: %s" %
(APPSLISTS_JSON, str(e)))
raise YunohostError("Error while writing list of appslist %s: %s" %
(APPSLISTS_JSON, str(e)), raw_msg=True)
def _register_new_appslist(url, name):
@ -2487,15 +2470,13 @@ def _register_new_appslist(url, name):
# Check if name conflicts with an existing list
if name in appslist_list:
raise MoulinetteError(errno.EEXIST,
m18n.n('appslist_name_already_tracked', name=name))
raise YunohostError('appslist_name_already_tracked', name=name)
# Check if url conflicts with an existing list
known_appslist_urls = [appslist["url"] for _, appslist in appslist_list.items()]
if url in known_appslist_urls:
raise MoulinetteError(errno.EEXIST,
m18n.n('appslist_url_already_tracked', url=url))
raise YunohostError('appslist_url_already_tracked', url=url)
logger.debug("Registering new appslist %s at %s" % (name, url))
@ -2586,7 +2567,7 @@ def _patch_php5(app_folder):
continue
c = "sed -i -e 's@/etc/php5@/etc/php/7.0@g' " \
"-e 's@/var/run/php5-fpm@/var/run/php/php7.0-fpm@g' " \
"-e 's@php5@php7.0@g' " \
"%s" % filename
"-e 's@/var/run/php5-fpm@/var/run/php/php7.0-fpm@g' " \
"-e 's@php5@php7.0@g' " \
"%s" % filename
os.system(c)

View file

@ -26,18 +26,18 @@
import os
import re
import json
import errno
import time
import tarfile
import shutil
import subprocess
import csv
import tempfile
from datetime import datetime
from glob import glob
from collections import OrderedDict
from moulinette import msignals, m18n
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils import filesystem
from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_file
@ -52,6 +52,7 @@ from yunohost.monitor import binary_to_human
from yunohost.tools import tools_postinstall
from yunohost.service import service_regen_conf
from yunohost.log import OperationLogger
from functools import reduce
BACKUP_PATH = '/home/yunohost.backup'
ARCHIVES_PATH = '%s/archives' % BACKUP_PATH
@ -63,6 +64,7 @@ logger = getActionLogger('yunohost.backup')
class BackupRestoreTargetsManager(object):
"""
BackupRestoreTargetsManager manage the targets
in BackupManager and RestoreManager
@ -176,6 +178,7 @@ class BackupRestoreTargetsManager(object):
class BackupManager():
"""
This class collect files to backup in a list and apply one or several
backup method on it.
@ -267,9 +270,9 @@ class BackupManager():
self.work_dir = os.path.join(BACKUP_PATH, 'tmp', name)
self._init_work_dir()
###########################################################################
# Misc helpers #
###########################################################################
#
# Misc helpers #
#
@property
def info(self):
@ -299,7 +302,7 @@ class BackupManager():
(string) A backup name created from current date 'YYMMDD-HHMMSS'
"""
# FIXME: case where this name already exist
return time.strftime('%Y%m%d-%H%M%S')
return time.strftime('%Y%m%d-%H%M%S', time.gmtime())
def _init_work_dir(self):
"""Initialize preparation directory
@ -307,31 +310,30 @@ class BackupManager():
Ensure the working directory exists and is empty
exception:
backup_output_directory_not_empty -- (MoulinetteError) Raised if the
backup_output_directory_not_empty -- (YunohostError) Raised if the
directory was given by the user and isn't empty
(TODO) backup_cant_clean_tmp_working_directory -- (MoulinetteError)
(TODO) backup_cant_clean_tmp_working_directory -- (YunohostError)
Raised if the working directory isn't empty, is temporary and can't
be automaticcaly cleaned
(TODO) backup_cant_create_working_directory -- (MoulinetteError) Raised
(TODO) backup_cant_create_working_directory -- (YunohostError) Raised
if iyunohost can't create the working directory
"""
# FIXME replace isdir by exists ? manage better the case where the path
# exists
if not os.path.isdir(self.work_dir):
filesystem.mkdir(self.work_dir, 0750, parents=True, uid='admin')
filesystem.mkdir(self.work_dir, 0o750, parents=True, uid='admin')
elif self.is_tmp_work_dir:
logger.debug("temporary directory for backup '%s' already exists",
self.work_dir)
# FIXME May be we should clean the workdir here
raise MoulinetteError(
errno.EIO, m18n.n('backup_output_directory_not_empty'))
raise YunohostError('backup_output_directory_not_empty')
###########################################################################
# Backup target management #
###########################################################################
#
# Backup target management #
#
def set_system_targets(self, system_parts=[]):
"""
@ -381,9 +383,9 @@ class BackupManager():
logger.warning(m18n.n('backup_with_no_restore_script_for_app', app=app))
self.targets.set_result("apps", app, "Warning")
###########################################################################
# Management of files to backup / "The CSV" #
###########################################################################
#
# Management of files to backup / "The CSV" #
#
def _import_to_list_to_backup(self, tmp_csv):
"""
@ -466,9 +468,9 @@ class BackupManager():
logger.error(m18n.n('backup_csv_addition_failed'))
self.csv_file.close()
###########################################################################
# File collection from system parts and apps #
###########################################################################
#
# File collection from system parts and apps #
#
def collect_files(self):
"""
@ -493,7 +495,7 @@ class BackupManager():
copied here
Exceptions:
"backup_nothings_done" -- (MoulinetteError) This exception is raised if
"backup_nothings_done" -- (YunohostError) This exception is raised if
nothing has been listed.
"""
@ -506,7 +508,7 @@ class BackupManager():
if not successfull_apps and not successfull_system:
filesystem.rm(self.work_dir, True, True)
raise MoulinetteError(errno.EINVAL, m18n.n('backup_nothings_done'))
raise YunohostError('backup_nothings_done')
# Add unlisted files from backup tmp dir
self._add_to_list_to_backup('backup.csv')
@ -603,7 +605,7 @@ class BackupManager():
restore_hooks_dir = os.path.join(self.work_dir, "hooks", "restore")
if not os.path.exists(restore_hooks_dir):
filesystem.mkdir(restore_hooks_dir, mode=0750,
filesystem.mkdir(restore_hooks_dir, mode=0o750,
parents=True, uid='admin')
restore_hooks = hook_list("restore")["hooks"]
@ -669,7 +671,7 @@ class BackupManager():
logger.debug(m18n.n('backup_running_app_script', app=app))
try:
# Prepare backup directory for the app
filesystem.mkdir(tmp_app_bkp_dir, 0750, True, uid='admin')
filesystem.mkdir(tmp_app_bkp_dir, 0o750, True, uid='admin')
# Copy the app settings to be able to call _common.sh
shutil.copytree(app_setting_path, settings_dir)
@ -703,9 +705,9 @@ class BackupManager():
filesystem.rm(tmp_script, force=True)
filesystem.rm(env_dict["YNH_BACKUP_CSV"], force=True)
###########################################################################
# Actual backup archive creation / method management #
###########################################################################
#
# Actual backup archive creation / method management #
#
def add(self, method):
"""
@ -777,6 +779,7 @@ class BackupManager():
class RestoreManager():
"""
RestoreManager allow to restore a past backup archive
@ -825,9 +828,9 @@ class RestoreManager():
self.method = BackupMethod.create(method)
self.targets = BackupRestoreTargetsManager()
###########################################################################
# Misc helpers #
###########################################################################
#
# Misc helpers #
#
@property
def success(self):
@ -856,10 +859,10 @@ class RestoreManager():
self.info["system"] = self.info["hooks"]
except IOError:
logger.debug("unable to load '%s'", info_file, exc_info=1)
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
raise YunohostError('backup_invalid_archive')
else:
logger.debug("restoring from backup '%s' created on %s", self.name,
time.ctime(self.info['created_at']))
datetime.utcfromtimestamp(self.info['created_at']))
def _postinstall_if_needed(self):
"""
@ -877,10 +880,9 @@ class RestoreManager():
domain = f.readline().rstrip()
except IOError:
logger.debug("unable to retrieve current_host from the backup",
exc_info=1)
exc_info=1)
# FIXME include the current_host by default ?
raise MoulinetteError(errno.EIO,
m18n.n('backup_invalid_archive'))
raise YunohostError('backup_invalid_archive')
logger.debug("executing the post-install...")
tools_postinstall(domain, 'yunohost', True)
@ -904,9 +906,9 @@ class RestoreManager():
logger.warning(m18n.n('restore_cleaning_failed'))
filesystem.rm(self.work_dir, True, True)
###########################################################################
# Restore target manangement #
###########################################################################
#
# Restore target manangement #
#
def set_system_targets(self, system_parts=[]):
"""
@ -982,9 +984,9 @@ class RestoreManager():
self.info['apps'].keys(),
unknown_error)
###########################################################################
# Archive mounting #
###########################################################################
#
# Archive mounting #
#
def mount(self):
"""
@ -1009,8 +1011,7 @@ class RestoreManager():
subprocess.call(['rmdir', self.work_dir])
logger.debug("Unmount dir: {}".format(self.work_dir))
else:
raise MoulinetteError(errno.EIO,
m18n.n('restore_removing_tmp_dir_failed'))
raise YunohostError('restore_removing_tmp_dir_failed')
elif os.path.isdir(self.work_dir):
logger.debug("temporary restore directory '%s' already exists",
self.work_dir)
@ -1018,8 +1019,7 @@ class RestoreManager():
if ret == 0:
logger.debug("Delete dir: {}".format(self.work_dir))
else:
raise MoulinetteError(errno.EIO,
m18n.n('restore_removing_tmp_dir_failed'))
raise YunohostError('restore_removing_tmp_dir_failed')
filesystem.mkdir(self.work_dir, parents=True)
@ -1027,9 +1027,9 @@ class RestoreManager():
self._read_info_files()
###########################################################################
# Space computation / checks #
###########################################################################
#
# Space computation / checks #
#
def _compute_needed_space(self):
"""
@ -1086,21 +1086,13 @@ class RestoreManager():
return True
elif free_space > needed_space:
# TODO Add --force options to avoid the error raising
raise MoulinetteError(errno.EIO,
m18n.n('restore_may_be_not_enough_disk_space',
free_space=free_space,
needed_space=needed_space,
margin=margin))
raise YunohostError('restore_may_be_not_enough_disk_space', free_space=free_space, needed_space=needed_space, margin=margin)
else:
raise MoulinetteError(errno.EIO,
m18n.n('restore_not_enough_disk_space',
free_space=free_space,
needed_space=needed_space,
margin=margin))
raise YunohostError('restore_not_enough_disk_space', free_space=free_space, needed_space=needed_space, margin=margin)
###########################################################################
# "Actual restore" (reverse step of the backup collect part) #
###########################################################################
#
# "Actual restore" (reverse step of the backup collect part) #
#
def restore(self):
"""
@ -1116,7 +1108,6 @@ class RestoreManager():
# Apply dirty patch to redirect php5 file on php7
self._patch_backup_csv_file()
self._restore_system()
self._restore_apps()
finally:
@ -1142,13 +1133,11 @@ class RestoreManager():
contains_php5 = True
row['source'] = row['source'].replace('/etc/php5', '/etc/php/7.0') \
.replace('/var/run/php5-fpm', '/var/run/php/php7.0-fpm') \
.replace('php5','php7')
.replace('php5', 'php7')
newlines.append(row)
except (IOError, OSError, csv.Error) as e:
raise MoulinetteError(errno.EIO,m18n.n('error_reading_file',
file=backup_csv,
error=str(e)))
raise YunohostError('error_reading_file', file=backup_csv, error=str(e))
if not contains_php5:
return
@ -1274,7 +1263,7 @@ class RestoreManager():
# Check if the app has a restore script
app_restore_script_in_archive = os.path.join(app_scripts_in_archive,
'restore')
'restore')
if not os.path.isfile(app_restore_script_in_archive):
logger.warning(m18n.n('unrestore_app', app=app_instance_name))
self.targets.set_result("apps", app_instance_name, "Warning")
@ -1287,7 +1276,7 @@ class RestoreManager():
app_instance_name)
app_scripts_new_path = os.path.join(app_settings_new_path, 'scripts')
shutil.copytree(app_settings_in_archive, app_settings_new_path)
filesystem.chmod(app_settings_new_path, 0400, 0400, True)
filesystem.chmod(app_settings_new_path, 0o400, 0o400, True)
filesystem.chown(app_scripts_new_path, 'admin', None, True)
# Copy the app scripts to a writable temporary folder
@ -1295,7 +1284,7 @@ class RestoreManager():
# in the backup method ?
tmp_folder_for_app_restore = tempfile.mkdtemp(prefix='restore')
copytree(app_scripts_in_archive, tmp_folder_for_app_restore)
filesystem.chmod(tmp_folder_for_app_restore, 0550, 0550, True)
filesystem.chmod(tmp_folder_for_app_restore, 0o550, 0o550, True)
filesystem.chown(tmp_folder_for_app_restore, 'admin', None, True)
restore_script = os.path.join(tmp_folder_for_app_restore, 'restore')
@ -1312,7 +1301,7 @@ class RestoreManager():
raise_on_error=True,
env=env_dict)
except:
msg = m18n.n('restore_app_failed',app=app_instance_name)
msg = m18n.n('restore_app_failed', app=app_instance_name)
logger.exception(msg)
operation_logger.error(msg)
@ -1328,8 +1317,8 @@ class RestoreManager():
env_dict_remove["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
operation_logger = OperationLogger('remove_on_failed_restore',
[('app', app_instance_name)],
env=env_dict_remove)
[('app', app_instance_name)],
env=env_dict_remove)
operation_logger.start()
# Execute remove script
@ -1373,12 +1362,13 @@ class RestoreManager():
return env_var
###############################################################################
# Backup methods #
###############################################################################
#
# Backup methods #
#
class BackupMethod(object):
"""
BackupMethod is an abstract class that represents a way to backup and
restore a list of files.
@ -1441,7 +1431,7 @@ class BackupMethod(object):
@property
def method_name(self):
"""Return the string name of a BackupMethod (eg "tar" or "copy")"""
raise MoulinetteError(errno.EINVAL, m18n.n('backup_abstract_method'))
raise YunohostError('backup_abstract_method')
@property
def name(self):
@ -1523,8 +1513,7 @@ class BackupMethod(object):
"""
if self.need_mount():
if self._recursive_umount(self.work_dir) > 0:
raise MoulinetteError(errno.EINVAL,
m18n.n('backup_cleaning_failed'))
raise YunohostError('backup_cleaning_failed')
if self.manager.is_tmp_work_dir:
filesystem.rm(self.work_dir, True, True)
@ -1566,8 +1555,7 @@ class BackupMethod(object):
if free_space < backup_size:
logger.debug('Not enough space at %s (free: %s / needed: %d)',
self.repo, free_space, backup_size)
raise MoulinetteError(errno.EIO, m18n.n(
'not_enough_disk_space', path=self.repo))
raise YunohostError('not_enough_disk_space', path=self.repo)
def _organize_files(self):
"""
@ -1653,18 +1641,16 @@ class BackupMethod(object):
if size > MB_ALLOWED_TO_ORGANIZE:
try:
i = msignals.prompt(m18n.n('backup_ask_for_copying_if_needed',
answers='y/N', size=str(size)))
answers='y/N', size=str(size)))
except NotImplemented:
raise MoulinetteError(errno.EIO,
m18n.n('backup_unable_to_organize_files'))
raise YunohostError('backup_unable_to_organize_files')
else:
if i != 'y' and i != 'Y':
raise MoulinetteError(errno.EIO,
m18n.n('backup_unable_to_organize_files'))
raise YunohostError('backup_unable_to_organize_files')
# Copy unbinded path
logger.debug(m18n.n('backup_copying_to_organize_the_archive',
size=str(size)))
size=str(size)))
for path in paths_needed_to_be_copied:
dest = os.path.join(self.work_dir, path['dest'])
if os.path.isdir(path['source']):
@ -1704,6 +1690,7 @@ class BackupMethod(object):
class CopyBackupMethod(BackupMethod):
"""
This class just do an uncompress copy of each file in a location, and
could be the inverse for restoring
@ -1730,7 +1717,7 @@ class CopyBackupMethod(BackupMethod):
dest_parent = os.path.dirname(dest)
if not os.path.exists(dest_parent):
filesystem.mkdir(dest_parent, 0750, True, uid='admin')
filesystem.mkdir(dest_parent, 0o750, True, uid='admin')
if os.path.isdir(source):
shutil.copytree(source, dest)
@ -1750,8 +1737,7 @@ class CopyBackupMethod(BackupMethod):
super(CopyBackupMethod, self).mount()
if not os.path.isdir(self.repo):
raise MoulinetteError(errno.EIO,
m18n.n('backup_no_uncompress_archive_dir'))
raise YunohostError('backup_no_uncompress_archive_dir')
filesystem.mkdir(self.work_dir, parent=True)
ret = subprocess.call(["mount", "-r", "--rbind", self.repo,
@ -1762,11 +1748,11 @@ class CopyBackupMethod(BackupMethod):
logger.warning(m18n.n("bind_mouting_disable"))
subprocess.call(["mountpoint", "-q", dest,
"&&", "umount", "-R", dest])
raise MoulinetteError(errno.EIO,
m18n.n('backup_cant_mount_uncompress_archive'))
raise YunohostError('backup_cant_mount_uncompress_archive')
class TarBackupMethod(BackupMethod):
"""
This class compress all files to backup in archive.
"""
@ -1797,7 +1783,7 @@ class TarBackupMethod(BackupMethod):
"""
if not os.path.exists(self.repo):
filesystem.mkdir(self.repo, 0750, parents=True, uid='admin')
filesystem.mkdir(self.repo, 0o750, parents=True, uid='admin')
# Check free space in output
self._check_is_enough_free_space()
@ -1808,8 +1794,7 @@ class TarBackupMethod(BackupMethod):
except:
logger.debug("unable to open '%s' for writing",
self._archive_file, exc_info=1)
raise MoulinetteError(errno.EIO,
m18n.n('backup_archive_open_failed'))
raise YunohostError('backup_archive_open_failed')
# Add files to the archive
try:
@ -1820,8 +1805,7 @@ class TarBackupMethod(BackupMethod):
tar.close()
except IOError:
logger.error(m18n.n('backup_archive_writing_error'), exc_info=1)
raise MoulinetteError(errno.EIO,
m18n.n('backup_creation_failed'))
raise YunohostError('backup_creation_failed')
# Move info file
shutil.copy(os.path.join(self.work_dir, 'info.json'),
@ -1849,8 +1833,7 @@ class TarBackupMethod(BackupMethod):
except:
logger.debug("cannot open backup archive '%s'",
self._archive_file, exc_info=1)
raise MoulinetteError(errno.EIO,
m18n.n('backup_archive_open_failed'))
raise YunohostError('backup_archive_open_failed')
tar.close()
# Mount the tarball
@ -1911,15 +1894,14 @@ class BorgBackupMethod(BackupMethod):
super(CopyBackupMethod, self).backup()
# TODO run borg create command
raise MoulinetteError(
errno.EIO, m18n.n('backup_borg_not_implemented'))
raise YunohostError('backup_borg_not_implemented')
def mount(self, mnt_path):
raise MoulinetteError(
errno.EIO, m18n.n('backup_borg_not_implemented'))
raise YunohostError('backup_borg_not_implemented')
class CustomBackupMethod(BackupMethod):
"""
This class use a bash script/hook "backup_method" to do the
backup/restore operations. A user can add his own hook inside
@ -1962,8 +1944,7 @@ class CustomBackupMethod(BackupMethod):
ret = hook_callback('backup_method', [self.method],
args=self._get_args('backup'))
if ret['failed']:
raise MoulinetteError(errno.EIO,
m18n.n('backup_custom_backup_error'))
raise YunohostError('backup_custom_backup_error')
def mount(self, restore_manager):
"""
@ -1976,8 +1957,7 @@ class CustomBackupMethod(BackupMethod):
ret = hook_callback('backup_method', [self.method],
args=self._get_args('mount'))
if ret['failed']:
raise MoulinetteError(errno.EIO,
m18n.n('backup_custom_mount_error'))
raise YunohostError('backup_custom_mount_error')
def _get_args(self, action):
"""Return the arguments to give to the custom script"""
@ -1985,9 +1965,9 @@ class CustomBackupMethod(BackupMethod):
self.manager.description]
###############################################################################
# "Front-end" #
###############################################################################
#
# "Front-end" #
#
def backup_create(name=None, description=None, methods=[],
output_directory=None, no_compress=False,
@ -2007,14 +1987,13 @@ def backup_create(name=None, description=None, methods=[],
# TODO: Add a 'clean' argument to clean output directory
###########################################################################
# Validate / parse arguments #
###########################################################################
#
# Validate / parse arguments #
#
# Validate there is no archive with the same name
if name and name in backup_list()['archives']:
raise MoulinetteError(errno.EINVAL,
m18n.n('backup_archive_name_exists'))
raise YunohostError('backup_archive_name_exists')
# Validate output_directory option
if output_directory:
@ -2023,18 +2002,15 @@ def backup_create(name=None, description=None, methods=[],
# Check for forbidden folders
if output_directory.startswith(ARCHIVES_PATH) or \
re.match(r'^/(|(bin|boot|dev|etc|lib|root|run|sbin|sys|usr|var)(|/.*))$',
output_directory):
raise MoulinetteError(errno.EINVAL,
m18n.n('backup_output_directory_forbidden'))
output_directory):
raise YunohostError('backup_output_directory_forbidden')
# Check that output directory is empty
if os.path.isdir(output_directory) and no_compress and \
os.listdir(output_directory):
raise MoulinetteError(errno.EIO,
m18n.n('backup_output_directory_not_empty'))
raise YunohostError('backup_output_directory_not_empty')
elif no_compress:
raise MoulinetteError(errno.EINVAL,
m18n.n('backup_output_directory_required'))
raise YunohostError('backup_output_directory_required')
# Define methods (retro-compat)
if not methods:
@ -2048,9 +2024,9 @@ def backup_create(name=None, description=None, methods=[],
system = []
apps = []
###########################################################################
# Intialize #
###########################################################################
#
# Intialize #
#
# Create yunohost archives directory if it does not exists
_create_archive_dir()
@ -2075,9 +2051,9 @@ def backup_create(name=None, description=None, methods=[],
backup_manager.set_system_targets(system)
backup_manager.set_apps_targets(apps)
###########################################################################
# Collect files and put them in the archive #
###########################################################################
#
# Collect files and put them in the archive #
#
# Collect files to be backup (by calling app backup script / system hooks)
backup_manager.collect_files()
@ -2105,9 +2081,9 @@ def backup_restore(auth, name, system=[], apps=[], force=False):
apps -- List of application names to restore
"""
###########################################################################
# Validate / parse arguments #
###########################################################################
#
# Validate / parse arguments #
#
# If no --system or --apps given, restore everything
if system is None and apps is None:
@ -2131,14 +2107,14 @@ def backup_restore(auth, name, system=[], apps=[], force=False):
if i == 'y' or i == 'Y':
force = True
if not force:
raise MoulinetteError(errno.EEXIST, m18n.n('restore_failed'))
raise YunohostError('restore_failed')
# TODO Partial app restore could not work if ldap is not restored before
# TODO repair mysql if broken and it's a complete restore
###########################################################################
# Initialize #
###########################################################################
#
# Initialize #
#
restore_manager = RestoreManager(name)
@ -2147,9 +2123,9 @@ def backup_restore(auth, name, system=[], apps=[], force=False):
restore_manager.assert_enough_free_space()
###########################################################################
# Mount the archive then call the restore for each system part / app #
###########################################################################
#
# Mount the archive then call the restore for each system part / app #
#
restore_manager.mount()
restore_manager.restore()
@ -2158,7 +2134,7 @@ def backup_restore(auth, name, system=[], apps=[], force=False):
if restore_manager.success:
logger.success(m18n.n('restore_complete'))
else:
raise MoulinetteError(errno.EINVAL, m18n.n('restore_nothings_done'))
raise YunohostError('restore_nothings_done')
return restore_manager.targets.results
@ -2187,14 +2163,14 @@ def backup_list(with_info=False, human_readable=False):
except ValueError:
continue
result.append(name)
result.sort(key=lambda x: os.path.getctime(os.path.join(ARCHIVES_PATH, x+".tar.gz")))
result.sort(key=lambda x: os.path.getctime(os.path.join(ARCHIVES_PATH, x + ".tar.gz")))
if result and with_info:
d = OrderedDict()
for a in result:
try:
d[a] = backup_info(a, human_readable=human_readable)
except MoulinetteError, e:
except YunohostError as e:
logger.warning('%s: %s' % (a, e.strerror))
result = d
@ -2216,8 +2192,7 @@ def backup_info(name, with_details=False, human_readable=False):
# Check file exist (even if it's a broken symlink)
if not os.path.lexists(archive_file):
raise MoulinetteError(errno.EIO,
m18n.n('backup_archive_name_unknown', name=name))
raise YunohostError('backup_archive_name_unknown', name=name)
# If symlink, retrieve the real path
if os.path.islink(archive_file):
@ -2225,9 +2200,8 @@ def backup_info(name, with_details=False, human_readable=False):
# Raise exception if link is broken (e.g. on unmounted external storage)
if not os.path.exists(archive_file):
raise MoulinetteError(errno.EIO,
m18n.n('backup_archive_broken_link',
path=archive_file))
raise YunohostError('backup_archive_broken_link',
path=archive_file)
info_file = "%s/%s.info.json" % (ARCHIVES_PATH, name)
@ -2239,7 +2213,7 @@ def backup_info(name, with_details=False, human_readable=False):
except KeyError:
logger.debug("unable to retrieve '%s' inside the archive",
info_file, exc_info=1)
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
raise YunohostError('backup_invalid_archive')
else:
shutil.move(os.path.join(info_dir, 'info.json'), info_file)
finally:
@ -2252,7 +2226,7 @@ def backup_info(name, with_details=False, human_readable=False):
info = json.load(f)
except:
logger.debug("unable to load '%s'", info_file, exc_info=1)
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
raise YunohostError('backup_invalid_archive')
# Retrieve backup size
size = info.get('size', 0)
@ -2266,8 +2240,7 @@ def backup_info(name, with_details=False, human_readable=False):
result = {
'path': archive_file,
'created_at': time.strftime(m18n.n('format_datetime_short'),
time.gmtime(info['created_at'])),
'created_at': datetime.utcfromtimestamp(info['created_at']),
'description': info['description'],
'size': size,
}
@ -2292,8 +2265,8 @@ def backup_delete(name):
"""
if name not in backup_list()["archives"]:
raise MoulinetteError(errno.EIO, m18n.n('backup_archive_name_unknown',
name=name))
raise YunohostError('backup_archive_name_unknown',
name=name)
hook_callback('pre_backup_delete', args=[name])
@ -2311,20 +2284,18 @@ def backup_delete(name):
logger.success(m18n.n('backup_deleted'))
###############################################################################
# Misc helpers #
###############################################################################
#
# Misc helpers #
#
def _create_archive_dir():
""" Create the YunoHost archives directory if doesn't exist """
if not os.path.isdir(ARCHIVES_PATH):
if os.path.lexists(ARCHIVES_PATH):
raise MoulinetteError(errno.EINVAL,
m18n.n('backup_output_symlink_dir_broken',
path=ARCHIVES_PATH))
raise YunohostError('backup_output_symlink_dir_broken', path=ARCHIVES_PATH)
os.mkdir(ARCHIVES_PATH, 0750)
os.mkdir(ARCHIVES_PATH, 0o750)
def _call_for_each_path(self, callback, csv_path=None):

View file

@ -24,7 +24,6 @@
import os
import sys
import errno
import shutil
import pwd
import grp
@ -37,7 +36,7 @@ from datetime import datetime
from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
from yunohost.utils.network import get_public_ip
@ -81,9 +80,9 @@ DNS_RESOLVERS = [
"80.67.188.188" # LDN
]
###############################################################################
# Front-end stuff #
###############################################################################
#
# Front-end stuff #
#
def certificate_status(auth, domain_list, full=False):
@ -106,8 +105,7 @@ def certificate_status(auth, domain_list, full=False):
for domain in domain_list:
# Is it in Yunohost domain list?
if domain not in yunohost_domains_list:
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_unknown', domain=domain))
raise YunohostError('certmanager_domain_unknown', domain=domain)
certificates = {}
@ -151,10 +149,10 @@ def _certificate_install_selfsigned(domain_list, force=False):
for domain in domain_list:
operation_logger = OperationLogger('selfsigned_cert_install', [('domain', domain)],
args={'force': force})
args={'force': force})
# Paths of files and folder we'll need
date_tag = datetime.now().strftime("%Y%m%d.%H%M%S")
date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S")
new_cert_folder = "%s/%s-history/%s-selfsigned" % (
CERT_FOLDER, domain, date_tag)
@ -172,8 +170,7 @@ def _certificate_install_selfsigned(domain_list, force=False):
status = _get_status(domain)
if status["summary"]["code"] in ('good', 'great'):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_attempt_to_replace_valid_cert', domain=domain))
raise YunohostError('certmanager_attempt_to_replace_valid_cert', domain=domain)
operation_logger.start()
@ -203,8 +200,7 @@ def _certificate_install_selfsigned(domain_list, force=False):
if p.returncode != 0:
logger.warning(out)
raise MoulinetteError(
errno.EIO, m18n.n('domain_cert_gen_failed'))
raise YunohostError('domain_cert_gen_failed')
else:
logger.debug(out)
@ -219,10 +215,10 @@ def _certificate_install_selfsigned(domain_list, force=False):
crt_pem.write(ca_pem.read())
# Set appropriate permissions
_set_permissions(new_cert_folder, "root", "root", 0755)
_set_permissions(key_file, "root", "ssl-cert", 0640)
_set_permissions(crt_file, "root", "ssl-cert", 0640)
_set_permissions(conf_file, "root", "root", 0600)
_set_permissions(new_cert_folder, "root", "root", 0o755)
_set_permissions(key_file, "root", "ssl-cert", 0o640)
_set_permissions(crt_file, "root", "ssl-cert", 0o640)
_set_permissions(conf_file, "root", "root", 0o600)
# Actually enable the certificate we created
_enable_certificate(domain, new_cert_folder)
@ -262,14 +258,12 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F
for domain in domain_list:
yunohost_domains_list = yunohost.domain.domain_list(auth)['domains']
if domain not in yunohost_domains_list:
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_unknown', domain=domain))
raise YunohostError('certmanager_domain_unknown', domain=domain)
# Is it self-signed?
status = _get_status(domain)
if not force and status["CA_type"]["code"] != "self-signed":
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_cert_not_selfsigned', domain=domain))
raise YunohostError('certmanager_domain_cert_not_selfsigned', domain=domain)
if staging:
logger.warning(
@ -279,8 +273,8 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F
for domain in domain_list:
operation_logger = OperationLogger('letsencrypt_cert_install', [('domain', domain)],
args={'force': force, 'no_checks': no_checks,
'staging': staging})
args={'force': force, 'no_checks': no_checks,
'staging': staging})
logger.info(
"Now attempting install of certificate for domain %s!", domain)
@ -304,6 +298,7 @@ def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=F
logger.error(msg)
operation_logger.error(msg)
def certificate_renew(auth, domain_list, force=False, no_checks=False, email=False, staging=False):
"""
Renew Let's Encrypt certificate for given domains (all by default)
@ -349,25 +344,21 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal
# Is it in Yunohost dmomain list?
if domain not in yunohost.domain.domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_unknown', domain=domain))
raise YunohostError('certmanager_domain_unknown', domain=domain)
status = _get_status(domain)
# Does it expire soon?
if status["validity"] > VALIDITY_LIMIT and not force:
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_attempt_to_renew_valid_cert', domain=domain))
raise YunohostError('certmanager_attempt_to_renew_valid_cert', domain=domain)
# Does it have a Let's Encrypt cert?
if status["CA_type"]["code"] != "lets-encrypt":
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_attempt_to_renew_nonLE_cert', domain=domain))
raise YunohostError('certmanager_attempt_to_renew_nonLE_cert', domain=domain)
# Check ACME challenge configured for given domain
if not _check_acme_challenge_configuration(domain):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_acme_not_configured_for_domain', domain=domain))
raise YunohostError('certmanager_acme_not_configured_for_domain', domain=domain)
if staging:
logger.warning(
@ -377,8 +368,8 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal
for domain in domain_list:
operation_logger = OperationLogger('letsencrypt_cert_renew', [('domain', domain)],
args={'force': force, 'no_checks': no_checks,
'staging': staging, 'email': email})
args={'force': force, 'no_checks': no_checks,
'staging': staging, 'email': email})
logger.info(
"Now attempting renewing of certificate for domain %s !", domain)
@ -411,9 +402,10 @@ def certificate_renew(auth, domain_list, force=False, no_checks=False, email=Fal
logger.error("Sending email with details to root ...")
_email_renewing_failed(domain, e, stack.getvalue())
###############################################################################
# Back-end stuff #
###############################################################################
#
# Back-end stuff #
#
def _install_cron():
cron_job_file = "/etc/cron.daily/yunohost-certificate-renew"
@ -422,7 +414,7 @@ def _install_cron():
f.write("#!/bin/bash\n")
f.write("yunohost domain cert-renew --email\n")
_set_permissions(cron_job_file, "root", "root", 0755)
_set_permissions(cron_job_file, "root", "root", 0o755)
def _email_renewing_failed(domain, exception_message, stack):
@ -484,8 +476,7 @@ location ^~ '/.well-known/acme-challenge/'
contents = f.read()
if '/.well-known/acme-challenge' in contents:
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_conflicting_nginx_file', filepath=path))
raise YunohostError('certmanager_conflicting_nginx_file', filepath=path)
# Write the conf
if os.path.exists(nginx_conf_file):
@ -528,8 +519,8 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False):
if not os.path.exists(TMP_FOLDER):
os.makedirs(TMP_FOLDER)
_set_permissions(WEBROOT_FOLDER, "root", "www-data", 0650)
_set_permissions(TMP_FOLDER, "root", "root", 0640)
_set_permissions(WEBROOT_FOLDER, "root", "www-data", 0o650)
_set_permissions(TMP_FOLDER, "root", "root", 0o640)
# Regen conf for dnsmasq if needed
_regen_dnsmasq_if_needed()
@ -540,7 +531,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False):
domain_key_file = "%s/%s.pem" % (TMP_FOLDER, domain)
_generate_key(domain_key_file)
_set_permissions(domain_key_file, "root", "ssl-cert", 0640)
_set_permissions(domain_key_file, "root", "ssl-cert", 0o640)
_prepare_certificate_signing_request(domain, domain_key_file, TMP_FOLDER)
@ -563,31 +554,28 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False):
CA=certification_authority)
except ValueError as e:
if "urn:acme:error:rateLimited" in str(e):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_hit_rate_limit', domain=domain))
raise YunohostError('certmanager_hit_rate_limit', domain=domain)
else:
logger.error(str(e))
_display_debug_information(domain)
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_cert_signing_failed'))
raise YunohostError('certmanager_cert_signing_failed')
except Exception as e:
logger.error(str(e))
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_cert_signing_failed'))
raise YunohostError('certmanager_cert_signing_failed')
import requests # lazy loading this module for performance reasons
import requests # lazy loading this module for performance reasons
try:
intermediate_certificate = requests.get(INTERMEDIATE_CERTIFICATE_URL, timeout=30).text
except requests.exceptions.Timeout as e:
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_couldnt_fetch_intermediate_cert'))
raise YunohostError('certmanager_couldnt_fetch_intermediate_cert')
# Now save the key and signed certificate
logger.debug("Saving the key and signed certificate...")
# Create corresponding directory
date_tag = datetime.now().strftime("%Y%m%d.%H%M%S")
date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S")
if staging:
folder_flag = "staging"
@ -599,12 +587,12 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False):
os.makedirs(new_cert_folder)
_set_permissions(new_cert_folder, "root", "root", 0655)
_set_permissions(new_cert_folder, "root", "root", 0o655)
# Move the private key
domain_key_file_finaldest = os.path.join(new_cert_folder, "key.pem")
shutil.move(domain_key_file, domain_key_file_finaldest)
_set_permissions(domain_key_file_finaldest, "root", "ssl-cert", 0640)
_set_permissions(domain_key_file_finaldest, "root", "ssl-cert", 0o640)
# Write the cert
domain_cert_file = os.path.join(new_cert_folder, "crt.pem")
@ -613,7 +601,7 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False):
f.write(signed_certificate)
f.write(intermediate_certificate)
_set_permissions(domain_cert_file, "root", "ssl-cert", 0640)
_set_permissions(domain_cert_file, "root", "ssl-cert", 0o640)
if staging:
return
@ -624,12 +612,11 @@ def _fetch_and_enable_new_certificate(domain, staging=False, no_checks=False):
status_summary = _get_status(domain)["summary"]
if status_summary["code"] != "great":
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_certificate_fetching_or_enabling_failed', domain=domain))
raise YunohostError('certmanager_certificate_fetching_or_enabling_failed', domain=domain)
def _prepare_certificate_signing_request(domain, key_file, output_folder):
from OpenSSL import crypto # lazy loading this module for performance reasons
from OpenSSL import crypto # lazy loading this module for performance reasons
# Init a request
csr = crypto.X509Req()
@ -658,23 +645,21 @@ def _get_status(domain):
cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem")
if not os.path.isfile(cert_file):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_no_cert_file', domain=domain, file=cert_file))
raise YunohostError('certmanager_no_cert_file', domain=domain, file=cert_file)
from OpenSSL import crypto # lazy loading this module for performance reasons
from OpenSSL import crypto # lazy loading this module for performance reasons
try:
cert = crypto.load_certificate(
crypto.FILETYPE_PEM, open(cert_file).read())
except Exception as exception:
import traceback
traceback.print_exc(file=sys.stdout)
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_cannot_read_cert', domain=domain, file=cert_file, reason=exception))
raise YunohostError('certmanager_cannot_read_cert', domain=domain, file=cert_file, reason=exception)
cert_subject = cert.get_subject().CN
cert_issuer = cert.get_issuer().CN
valid_up_to = datetime.strptime(cert.get_notAfter(), "%Y%m%d%H%M%SZ")
days_remaining = (valid_up_to - datetime.now()).days
days_remaining = (valid_up_to - datetime.utcnow()).days
if cert_issuer == _name_self_CA():
CA_type = {
@ -752,19 +737,19 @@ def _get_status(domain):
"ACME_eligible": ACME_eligible
}
###############################################################################
# Misc small stuff ... #
###############################################################################
#
# Misc small stuff ... #
#
def _generate_account_key():
logger.debug("Generating account key ...")
_generate_key(ACCOUNT_KEY_FILE)
_set_permissions(ACCOUNT_KEY_FILE, "root", "root", 0400)
_set_permissions(ACCOUNT_KEY_FILE, "root", "root", 0o400)
def _generate_key(destination_path):
from OpenSSL import crypto # lazy loading this module for performance reasons
from OpenSSL import crypto # lazy loading this module for performance reasons
k = crypto.PKey()
k.generate_key(crypto.TYPE_RSA, KEY_SIZE)
@ -810,13 +795,16 @@ def _enable_certificate(domain, new_cert_folder):
_run_service_command("reload", "nginx")
from yunohost.hook import hook_callback
hook_callback('post_cert_update', args=[domain])
def _backup_current_cert(domain):
logger.debug("Backuping existing certificate for domain %s", domain)
cert_folder_domain = os.path.join(CERT_FOLDER, domain)
date_tag = datetime.now().strftime("%Y%m%d.%H%M%S")
date_tag = datetime.utcnow().strftime("%Y%m%d.%H%M%S")
backup_folder = "%s-backups/%s" % (cert_folder_domain, date_tag)
shutil.copytree(cert_folder_domain, backup_folder)
@ -827,13 +815,11 @@ def _check_domain_is_ready_for_ACME(domain):
# Check if IP from DNS matches public IP
if not _dns_ip_match_public_ip(public_ip, domain):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_dns_ip_differs_from_public_ip', domain=domain))
raise YunohostError('certmanager_domain_dns_ip_differs_from_public_ip', domain=domain)
# Check if domain seems to be accessible through HTTP?
if not _domain_is_accessible_through_HTTP(public_ip, domain):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_http_not_working', domain=domain))
raise YunohostError('certmanager_domain_http_not_working', domain=domain)
def _get_dns_ip(domain):
@ -842,8 +828,7 @@ def _get_dns_ip(domain):
resolver.nameservers = DNS_RESOLVERS
answers = resolver.query(domain, "A")
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_error_no_A_record', domain=domain))
raise YunohostError('certmanager_error_no_A_record', domain=domain)
return str(answers[0])
@ -853,7 +838,7 @@ def _dns_ip_match_public_ip(public_ip, domain):
def _domain_is_accessible_through_HTTP(ip, domain):
import requests # lazy loading this module for performance reasons
import requests # lazy loading this module for performance reasons
try:
requests.head("http://" + ip, headers={"Host": domain}, timeout=10)
except requests.exceptions.Timeout as e:

View file

@ -3,7 +3,9 @@ import glob
from yunohost.tools import Migration
from moulinette.utils.filesystem import chown
class MyMigration(Migration):
"Change certificates group permissions from 'metronome' to 'ssl-cert'"
all_certificate_files = glob.glob("/etc/yunohost/certs/*/*.pem")

View file

@ -4,10 +4,9 @@ import requests
import base64
import time
import json
import errno
from moulinette import m18n
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
from yunohost.tools import Migration
@ -17,6 +16,7 @@ logger = getActionLogger('yunohost.migration')
class MyMigration(Migration):
"Migrate Dyndns stuff from MD5 TSIG to SHA512 TSIG"
def backward(self):
@ -29,7 +29,7 @@ class MyMigration(Migration):
try:
(domain, private_key_path) = _guess_current_dyndns_domain(dyn_host)
assert "+157" in private_key_path
except (MoulinetteError, AssertionError):
except (YunohostError, AssertionError):
logger.info(m18n.n("migrate_tsig_not_needed"))
return
@ -52,7 +52,7 @@ class MyMigration(Migration):
'public_key_sha512': base64.b64encode(public_key_sha512),
}, timeout=30)
except requests.ConnectionError:
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
raise YunohostError('no_internet_connection')
if r.status_code != 201:
try:
@ -70,8 +70,8 @@ class MyMigration(Migration):
# Migration didn't succeed, so we rollback and raise an exception
os.system("mv /etc/yunohost/dyndns/*+165* /tmp")
raise MoulinetteError(m18n.n('migrate_tsig_failed', domain=domain,
error_code=str(r.status_code), error=error))
raise YunohostError('migrate_tsig_failed', domain=domain,
error_code=str(r.status_code), error=error)
# remove old certificates
os.system("mv /etc/yunohost/dyndns/*+157* /tmp")
@ -88,4 +88,3 @@ class MyMigration(Migration):
logger.info(m18n.n('migrate_tsig_end'))
return

View file

@ -3,7 +3,7 @@ import os
from shutil import copy2
from moulinette import m18n, msettings
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
from moulinette.utils.process import check_output, call_async_output
from moulinette.utils.filesystem import read_file
@ -24,13 +24,14 @@ YUNOHOST_PACKAGES = ["yunohost", "yunohost-admin", "moulinette", "ssowat"]
class MyMigration(Migration):
"Upgrade the system to Debian Stretch and Yunohost 3.0"
mode = "manual"
def backward(self):
raise MoulinetteError(m18n.n("migration_0003_backward_impossible"))
raise YunohostError("migration_0003_backward_impossible")
def migrate(self):
@ -57,7 +58,7 @@ class MyMigration(Migration):
self.apt_dist_upgrade(conf_flags=["old", "miss", "def"])
_run_service_command("start", "mysql")
if self.debian_major_version() == 8:
raise MoulinetteError(m18n.n("migration_0003_still_on_jessie_after_main_upgrade", log=self.logfile))
raise YunohostError("migration_0003_still_on_jessie_after_main_upgrade", log=self.logfile)
# Specific upgrade for fail2ban...
logger.info(m18n.n("migration_0003_fail2ban_upgrade"))
@ -107,11 +108,11 @@ class MyMigration(Migration):
# would still be in 2.x...
if not self.debian_major_version() == 8 \
and not self.yunohost_major_version() == 2:
raise MoulinetteError(m18n.n("migration_0003_not_jessie"))
raise YunohostError("migration_0003_not_jessie")
# Have > 1 Go free space on /var/ ?
if free_space_in_directory("/var/") / (1024**3) < 1.0:
raise MoulinetteError(m18n.n("migration_0003_not_enough_free_space"))
raise YunohostError("migration_0003_not_enough_free_space")
# Check system is up to date
# (but we don't if 'stretch' is already in the sources.list ...
@ -120,7 +121,7 @@ class MyMigration(Migration):
self.apt_update()
apt_list_upgradable = check_output("apt list --upgradable -a")
if "upgradable" in apt_list_upgradable:
raise MoulinetteError(m18n.n("migration_0003_system_not_fully_up_to_date"))
raise YunohostError("migration_0003_system_not_fully_up_to_date")
@property
def disclaimer(self):
@ -168,11 +169,11 @@ class MyMigration(Migration):
# - switch yunohost's repo to forge
for f in sources_list:
command = "sed -i -e 's@ jessie @ stretch @g' " \
"-e '/backports/ s@^#*@#@' " \
"-e 's@ jessie/updates @ stretch/updates @g' " \
"-e 's@ jessie-updates @ stretch-updates @g' " \
"-e 's@repo.yunohost@forge.yunohost@g' " \
"{}".format(f)
"-e '/backports/ s@^#*@#@' " \
"-e 's@ jessie/updates @ stretch/updates @g' " \
"-e 's@ jessie-updates @ stretch-updates @g' " \
"-e 's@repo.yunohost@forge.yunohost@g' " \
"{}".format(f)
os.system(command)
def get_apps_equivs_packages(self):
@ -286,7 +287,7 @@ class MyMigration(Migration):
# Create tmp directory if it does not exists
tmp_dir = os.path.join("/tmp/", self.name)
if not os.path.exists(tmp_dir):
os.mkdir(tmp_dir, 0700)
os.mkdir(tmp_dir, 0o700)
for f in self.files_to_keep:
dest_file = f.strip('/').replace("/", "_")

View file

@ -19,6 +19,7 @@ MIGRATION_COMMENT = "; YunoHost note : this file was automatically moved from {}
class MyMigration(Migration):
"Migrate php5-fpm 'pool' conf files to php7 stuff"
def migrate(self):
@ -58,7 +59,7 @@ class MyMigration(Migration):
_run_service_command("enable", "php7.0-fpm")
os.system("systemctl stop php5-fpm")
os.system("systemctl disable php5-fpm")
os.system("rm /etc/logrotate.d/php5-fpm") # We remove this otherwise the logrotate cron will be unhappy
os.system("rm /etc/logrotate.d/php5-fpm") # We remove this otherwise the logrotate cron will be unhappy
# Get list of nginx conf file
nginx_conf_files = glob.glob("/etc/nginx/conf.d/*.d/*.conf")

View file

@ -1,7 +1,7 @@
import subprocess
from moulinette import m18n
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
from yunohost.tools import Migration
@ -11,6 +11,7 @@ logger = getActionLogger('yunohost.migration')
class MyMigration(Migration):
"Migrate DBs from Postgresql 9.4 to 9.6 after migrating to Stretch"
def migrate(self):
@ -20,10 +21,10 @@ class MyMigration(Migration):
return
if not self.package_is_installed("postgresql-9.6"):
raise MoulinetteError(m18n.n("migration_0005_postgresql_96_not_installed"))
raise YunohostError("migration_0005_postgresql_96_not_installed")
if not space_used_by_directory("/var/lib/postgresql/9.4") > free_space_in_directory("/var/lib/postgresql"):
raise MoulinetteError(m18n.n("migration_0005_not_enough_space", path="/var/lib/postgresql/"))
raise YunohostError("migration_0005_not_enough_space", path="/var/lib/postgresql/")
subprocess.check_call("service postgresql stop", shell=True)
subprocess.check_call("pg_dropcluster --stop 9.6 main", shell=True)

View file

@ -5,7 +5,7 @@ import string
import subprocess
from moulinette import m18n
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
from moulinette.utils.process import run_commands, check_output
from moulinette.utils.filesystem import append_to_file
@ -15,7 +15,9 @@ from yunohost.tools import Migration
logger = getActionLogger('yunohost.migration')
SMALL_PWD_LIST = ["yunohost", "olinuxino", "olinux", "raspberry", "admin", "root", "test", "rpi"]
class MyMigration(Migration):
"Synchronize admin and root passwords"
def migrate(self):
@ -23,7 +25,7 @@ class MyMigration(Migration):
new_hash = self._get_admin_hash()
self._replace_root_hash(new_hash)
logger.info(m18n.n("migration_0006_done"))
logger.info(m18n.n("root_password_replaced_by_admin_password"))
def backward(self):
pass

View file

@ -0,0 +1,80 @@
import os
import re
from shutil import copyfile
from moulinette import m18n
from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import mkdir, rm
from yunohost.tools import Migration
from yunohost.service import service_regen_conf, \
_get_conf_hashes, \
_calculate_hash, \
_run_service_command
from yunohost.settings import settings_set
from yunohost.utils.error import YunohostError
logger = getActionLogger('yunohost.migration')
SSHD_CONF = '/etc/ssh/sshd_config'
class MyMigration(Migration):
"""
This is the first step of a couple of migrations that ensure SSH conf is
managed by YunoHost (even if the "from_script" flag is present, which was
previously preventing it from being managed by YunoHost)
The goal of this first (automatic) migration is to make sure that the
sshd_config is managed by the regen-conf mechanism.
If the from_script flag exists, then we keep the current SSH conf such that it
will appear as "manually modified" to the regenconf.
In step 2 (manual), the admin will be able to choose wether or not to actually
use the recommended configuration, with an appropriate disclaimer.
"""
def migrate(self):
# Check if deprecated DSA Host Key is in config
dsa_rgx = r'^[ \t]*HostKey[ \t]+/etc/ssh/ssh_host_dsa_key[ \t]*(?:#.*)?$'
dsa = False
for line in open(SSHD_CONF):
if re.match(dsa_rgx, line) is not None:
dsa = True
break
if dsa:
settings_set("service.ssh.allow_deprecated_dsa_hostkey", True)
# Create sshd_config.d dir
if not os.path.exists(SSHD_CONF + '.d'):
mkdir(SSHD_CONF + '.d', 0o755, uid='root', gid='root')
# Here, we make it so that /etc/ssh/sshd_config is managed
# by the regen conf (in particular in the case where the
# from_script flag is present - in which case it was *not*
# managed by the regenconf)
# But because we can't be sure the user wants to use the
# recommended conf, we backup then restore the /etc/ssh/sshd_config
# right after the regenconf, such that it will appear as
# "manually modified".
if os.path.exists('/etc/yunohost/from_script'):
rm('/etc/yunohost/from_script')
copyfile(SSHD_CONF, '/etc/ssh/sshd_config.bkp')
service_regen_conf(names=['ssh'], force=True)
copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF)
# Restart ssh and backward if it fail
if not _run_service_command('restart', 'ssh'):
self.backward()
raise YunohostError("migration_0007_cancel")
def backward(self):
# We don't backward completely but it should be enough
copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF)
if not _run_service_command('restart', 'ssh'):
raise YunohostError("migration_0007_cannot_restart")

View file

@ -0,0 +1,98 @@
import re
from moulinette.utils.log import getActionLogger
from yunohost.tools import Migration
from yunohost.service import service_regen_conf, \
_get_conf_hashes, \
_calculate_hash
from yunohost.settings import settings_set, settings_get
from yunohost.utils.error import YunohostError
logger = getActionLogger('yunohost.migration')
SSHD_CONF = '/etc/ssh/sshd_config'
class MyMigration(Migration):
"""
In this second step, the admin is asked if it's okay to use
the recommended SSH configuration - which also implies
disabling deprecated DSA key.
This has important implications in the way the user may connect
to its server (key change, and a spooky warning might be given
by SSH later)
A disclaimer explaining the various things to be aware of is
shown - and the user may also choose to skip this migration.
"""
def migrate(self):
settings_set("service.ssh.allow_deprecated_dsa_hostkey", False)
service_regen_conf(names=['ssh'], force=True)
def backward(self):
raise YunohostError("migration_0008_backward_impossible")
@property
def mode(self):
# If the conf is already up to date
# and no DSA key is used, then we're good to go
# and the migration can be done automatically
# (basically nothing shall change)
ynh_hash = _get_conf_hashes('ssh').get(SSHD_CONF, None)
current_hash = _calculate_hash(SSHD_CONF)
dsa = settings_get("service.ssh.allow_deprecated_dsa_hostkey")
if ynh_hash == current_hash and not dsa:
return "auto"
return "manual"
@property
def disclaimer(self):
if self.mode == "auto":
return None
# Detect key things to be aware of before enabling the
# recommended configuration
dsa_key_enabled = False
ports = []
root_login = []
port_rgx = r'^[ \t]*Port[ \t]+(\d+)[ \t]*(?:#.*)?$'
root_rgx = r'^[ \t]*PermitRootLogin[ \t]([^# \t]*)[ \t]*(?:#.*)?$'
dsa_rgx = r'^[ \t]*HostKey[ \t]+/etc/ssh/ssh_host_dsa_key[ \t]*(?:#.*)?$'
for line in open(SSHD_CONF):
ports = ports + re.findall(port_rgx, line)
root_login = root_login + re.findall(root_rgx, line)
if not dsa_key_enabled and re.match(dsa_rgx, line) is not None:
dsa_key_enabled = True
custom_port = ports != ['22'] and ports != []
root_login_enabled = root_login and root_login[-1] != 'no'
# Build message
message = m18n.n("migration_0008_general_disclaimer")
if custom_port:
message += "\n\n" + m18n.n("migration_0008_port")
if root_login_enabled:
message += "\n\n" + m18n.n("migration_0008_root")
if dsa_key_enabled:
message += "\n\n" + m18n.n("migration_0008_dsa")
if custom_port or root_login_enabled or dsa_key_enabled:
message += "\n\n" + m18n.n("migration_0008_warning")
else:
message += "\n\n" + m18n.n("migration_0008_no_warning")
return message

View file

@ -25,12 +25,11 @@
"""
import os
import re
import json
import yaml
import errno
from moulinette import m18n, msettings
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
import yunohost.certificate
@ -78,7 +77,7 @@ def domain_add(operation_logger, auth, domain, dyndns=False):
try:
auth.validate_uniqueness({'virtualdomain': domain})
except MoulinetteError:
raise MoulinetteError(errno.EEXIST, m18n.n('domain_exists'))
raise YunohostError('domain_exists')
operation_logger.start()
@ -87,16 +86,14 @@ def domain_add(operation_logger, auth, domain, dyndns=False):
# Do not allow to subscribe to multiple dyndns domains...
if os.path.exists('/etc/cron.d/yunohost-dyndns'):
raise MoulinetteError(errno.EPERM,
m18n.n('domain_dyndns_already_subscribed'))
raise YunohostError('domain_dyndns_already_subscribed')
from yunohost.dyndns import dyndns_subscribe, _dyndns_provides
# Check that this domain can effectively be provided by
# dyndns.yunohost.org. (i.e. is it a nohost.me / noho.st)
if not _dyndns_provides("dyndns.yunohost.org", domain):
raise MoulinetteError(errno.EINVAL,
m18n.n('domain_dyndns_root_unknown'))
raise YunohostError('domain_dyndns_root_unknown')
# Actually subscribe
dyndns_subscribe(domain=domain)
@ -110,23 +107,20 @@ def domain_add(operation_logger, auth, domain, dyndns=False):
}
if not auth.add('virtualdomain=%s,ou=domains' % domain, attr_dict):
raise MoulinetteError(errno.EIO, m18n.n('domain_creation_failed'))
raise YunohostError('domain_creation_failed')
# Don't regen these conf if we're still in postinstall
if os.path.exists('/etc/yunohost/installed'):
service_regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix'])
app_ssowatconf(auth)
except Exception, e:
from sys import exc_info;
t, v, tb = exc_info()
except Exception:
# Force domain removal silently
try:
domain_remove(auth, domain, True)
except:
pass
raise t, v, tb
raise
hook_callback('post_domain_add', args=[domain])
@ -147,11 +141,11 @@ def domain_remove(operation_logger, auth, domain, force=False):
from yunohost.app import app_ssowatconf
if not force and domain not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
raise YunohostError('domain_unknown')
# Check domain is not the main domain
if domain == _get_maindomain():
raise MoulinetteError(errno.EINVAL, m18n.n('domain_cannot_remove_main'))
raise YunohostError('domain_cannot_remove_main')
# Check if apps are installed on the domain
for app in os.listdir('/etc/yunohost/apps/'):
@ -162,14 +156,13 @@ def domain_remove(operation_logger, auth, domain, force=False):
continue
else:
if app_domain == domain:
raise MoulinetteError(errno.EPERM,
m18n.n('domain_uninstall_app_first'))
raise YunohostError('domain_uninstall_app_first')
operation_logger.start()
if auth.remove('virtualdomain=' + domain + ',ou=domains') or force:
os.system('rm -rf /etc/yunohost/certs/%s' % domain)
else:
raise MoulinetteError(errno.EIO, m18n.n('domain_deletion_failed'))
raise YunohostError('domain_deletion_failed')
service_regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix'])
app_ssowatconf(auth)
@ -246,7 +239,7 @@ def _get_conflicting_apps(auth, domain, path):
# Abort if domain is unknown
if domain not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
raise YunohostError('domain_unknown')
# This import cannot be put on top of file because it would create a
# recursive import...
@ -340,7 +333,7 @@ def _build_dns_conf(domain, ttl=3600):
{"type": "TXT", "name": "_dmarc", "value": "\"v=DMARC1; p=none\"", "ttl": 3600}
],
"extra": [
{"type": "CAA", "name": "@", "value": "128 issue 'letsencrypt.org'", "ttl": 3600},
{"type": "CAA", "name": "@", "value": "128 issue \"letsencrypt.org\"", "ttl": 3600},
],
}
"""
@ -397,7 +390,7 @@ def _build_dns_conf(domain, ttl=3600):
# Extra
extra = [
["@", ttl, "CAA", "128 issue 'letsencrypt.org'"]
["@", ttl, "CAA", '128 issue "letsencrypt.org"']
]
return {

View file

@ -27,19 +27,17 @@ import os
import re
import json
import glob
import time
import base64
import errno
import subprocess
from moulinette import m18n
from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_file, write_to_file, rm
from moulinette.utils.filesystem import write_to_file
from moulinette.utils.network import download_json
from moulinette.utils.process import check_output
from yunohost.utils.error import YunohostError
from yunohost.domain import _get_maindomain, _build_dns_conf
from yunohost.utils.network import get_public_ip
from yunohost.log import is_unit_operation
@ -77,9 +75,7 @@ def _dyndns_provides(provider, domain):
dyndomains = download_json('https://%s/domains' % provider, timeout=30)
except MoulinetteError as e:
logger.error(str(e))
raise MoulinetteError(errno.EIO,
m18n.n('dyndns_could_not_check_provide',
domain=domain, provider=provider))
raise YunohostError('dyndns_could_not_check_provide', domain=domain, provider=provider)
# Extract 'dyndomain' from 'domain', e.g. 'nohost.me' from 'foo.nohost.me'
dyndomain = '.'.join(domain.split('.')[1:])
@ -106,9 +102,8 @@ def _dyndns_available(provider, domain):
expected_status_code=None)
except MoulinetteError as e:
logger.error(str(e))
raise MoulinetteError(errno.EIO,
m18n.n('dyndns_could_not_check_available',
domain=domain, provider=provider))
raise YunohostError('dyndns_could_not_check_available',
domain=domain, provider=provider)
return r == u"Domain %s is available" % domain
@ -130,14 +125,11 @@ def dyndns_subscribe(operation_logger, subscribe_host="dyndns.yunohost.org", dom
# Verify if domain is provided by subscribe_host
if not _dyndns_provides(subscribe_host, domain):
raise MoulinetteError(errno.ENOENT,
m18n.n('dyndns_domain_not_provided',
domain=domain, provider=subscribe_host))
raise YunohostError('dyndns_domain_not_provided', domain=domain, provider=subscribe_host)
# Verify if domain is available
if not _dyndns_available(subscribe_host, domain):
raise MoulinetteError(errno.ENOENT,
m18n.n('dyndns_unavailable', domain=domain))
raise YunohostError('dyndns_unavailable', domain=domain)
operation_logger.start()
@ -156,19 +148,18 @@ def dyndns_subscribe(operation_logger, subscribe_host="dyndns.yunohost.org", dom
with open(key_file) as f:
key = f.readline().strip().split(' ', 6)[-1]
import requests # lazy loading this module for performance reasons
import requests # lazy loading this module for performance reasons
# Send subscription
try:
r = requests.post('https://%s/key/%s?key_algo=hmac-sha512' % (subscribe_host, base64.b64encode(key)), data={'subdomain': domain}, timeout=30)
except requests.ConnectionError:
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
raise YunohostError('no_internet_connection')
if r.status_code != 201:
try:
error = json.loads(r.text)['error']
except:
error = "Server error, code: %s. (Message: \"%s\")" % (r.status_code, r.text)
raise MoulinetteError(errno.EPERM,
m18n.n('dyndns_registration_failed', error=error))
raise YunohostError('dyndns_registration_failed', error=error)
logger.success(m18n.n('dyndns_registered'))
@ -202,7 +193,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
keys = glob.glob('/etc/yunohost/dyndns/K{0}.+*.private'.format(domain))
if not keys:
raise MoulinetteError(errno.EIO, m18n.n('dyndns_key_not_found'))
raise YunohostError('dyndns_key_not_found')
key = keys[0]
@ -219,7 +210,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
exception=e,
number=migration.number,
name=migration.name),
exc_info=1)
exc_info=1)
return
# Extract 'host', e.g. 'nohost.me' from 'foo.nohost.me'
@ -233,7 +224,6 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
'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
@ -260,7 +250,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
logger.info("Updated needed, going on...")
dns_conf = _build_dns_conf(domain)
del dns_conf["extra"] # Ignore records from the 'extra' category
del dns_conf["extra"] # Ignore records from the 'extra' category
# Delete the old records for all domain/subdomains
@ -281,7 +271,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
# should be muc.the.domain.tld. or the.domain.tld
if record["value"] == "@":
record["value"] = domain
record["value"] = record["value"].replace(";","\;")
record["value"] = record["value"].replace(";", "\;")
action = "update add {name}.{domain}. {ttl} {type} {value}".format(domain=domain, **record)
action = action.replace(" @.", " ")
@ -302,8 +292,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE]
subprocess.check_call(command)
except subprocess.CalledProcessError:
raise MoulinetteError(errno.EPERM,
m18n.n('dyndns_ip_update_failed'))
raise YunohostError('dyndns_ip_update_failed')
logger.success(m18n.n('dyndns_ip_updated'))
@ -329,7 +318,7 @@ def dyndns_removecron():
try:
os.remove("/etc/cron.d/yunohost-dyndns")
except:
raise MoulinetteError(errno.EIO, m18n.n('dyndns_cron_remove_failed'))
raise YunohostError('dyndns_cron_remove_failed')
logger.success(m18n.n('dyndns_cron_removed'))
@ -359,5 +348,4 @@ def _guess_current_dyndns_domain(dyn_host):
else:
return (_domain, path)
raise MoulinetteError(errno.EINVAL,
m18n.n('dyndns_no_domain_registered'))
raise YunohostError('dyndns_no_domain_registered')

View file

@ -26,7 +26,6 @@
import os
import sys
import yaml
import errno
try:
import miniupnpc
except ImportError:
@ -34,7 +33,7 @@ except ImportError:
sys.exit(1)
from moulinette import m18n
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils import process
from moulinette.utils.log import getActionLogger
from moulinette.utils.text import prependlines
@ -268,7 +267,7 @@ def firewall_reload(skip_upnp=False):
reloaded = True
if not reloaded:
raise MoulinetteError(errno.ESRCH, m18n.n('firewall_reload_failed'))
raise YunohostError('firewall_reload_failed')
hook_callback('post_iptable_rules',
args=[upnp, os.path.exists("/proc/net/if_inet6")])
@ -338,7 +337,7 @@ def firewall_upnp(action='status', no_refresh=False):
if action == 'status':
no_refresh = True
else:
raise MoulinetteError(errno.EINVAL, m18n.n('action_invalid', action=action))
raise YunohostError('action_invalid', action=action)
# Refresh port mapping using UPnP
if not no_refresh:
@ -375,10 +374,10 @@ def firewall_upnp(action='status', no_refresh=False):
try:
# Add new port mapping
upnpc.addportmapping(port, protocol, upnpc.lanaddr,
port, 'yunohost firewall: port %d' % port, '')
port, 'yunohost firewall: port %d' % port, '')
except:
logger.debug('unable to add port %d using UPnP',
port, exc_info=1)
port, exc_info=1)
enabled = False
if enabled != firewall['uPnP']['enabled']:
@ -407,7 +406,7 @@ def firewall_upnp(action='status', no_refresh=False):
firewall_reload(skip_upnp=True)
if action == 'enable' and not enabled:
raise MoulinetteError(errno.ENXIO, m18n.n('upnp_port_open_failed'))
raise YunohostError('upnp_port_open_failed')
return {'enabled': enabled}
@ -419,7 +418,7 @@ def firewall_stop():
"""
if os.system("iptables -w -P INPUT ACCEPT") != 0:
raise MoulinetteError(errno.ESRCH, m18n.n('iptables_unavailable'))
raise YunohostError('iptables_unavailable')
os.system("iptables -w -F")
os.system("iptables -w -X")

View file

@ -25,12 +25,11 @@
"""
import os
import re
import errno
import tempfile
from glob import iglob
from moulinette import m18n
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils import log
HOOK_FOLDER = '/usr/share/yunohost/hooks/'
@ -112,7 +111,7 @@ def hook_info(action, name):
})
if not hooks:
raise MoulinetteError(errno.EINVAL, m18n.n('hook_name_unknown', name=name))
raise YunohostError('hook_name_unknown', name=name)
return {
'action': action,
'name': name,
@ -174,7 +173,7 @@ def hook_list(action, list_by='name', show_info=False):
# Add only the name
d.add(name)
else:
raise MoulinetteError(errno.EINVAL, m18n.n('hook_list_by_invalid'))
raise YunohostError('hook_list_by_invalid')
def _append_folder(d, folder):
# Iterate over and add hook from a folder
@ -255,8 +254,7 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None,
try:
hl = hooks_names[n]
except KeyError:
raise MoulinetteError(errno.EINVAL,
m18n.n('hook_name_unknown', n))
raise YunohostError('hook_name_unknown', n)
# Iterate over hooks with this name
for h in hl:
# Update hooks dict
@ -282,7 +280,7 @@ def hook_callback(action, hooks=[], args=None, no_trace=False, chdir=None,
path=path, args=args)
hook_exec(path, args=hook_args, chdir=chdir, env=env,
no_trace=no_trace, raise_on_error=True)
except MoulinetteError as e:
except YunohostError as e:
state = 'failed'
logger.error(e.strerror, exc_info=1)
post_callback(name=name, priority=priority, path=path,
@ -319,7 +317,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
if path[0] != '/':
path = os.path.realpath(path)
if not os.path.isfile(path):
raise MoulinetteError(errno.EIO, m18n.g('file_not_exist', path=path))
raise YunohostError('file_not_exist', path=path)
# Construct command variables
cmd_args = ''
@ -356,7 +354,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
# prepend environment variables
cmd = '{0} {1}'.format(
' '.join(['{0}={1}'.format(k, shell_quote(v))
for k, v in env.items()]), cmd)
for k, v in env.items()]), cmd)
command.append(cmd.format(script=cmd_script, args=cmd_args))
if logger.isEnabledFor(log.DEBUG):
@ -371,8 +369,8 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
)
if stdinfo:
callbacks = ( callbacks[0], callbacks[1],
lambda l: logger.info(l.rstrip()))
callbacks = (callbacks[0], callbacks[1],
lambda l: logger.info(l.rstrip()))
logger.debug("About to run the command '%s'" % command)
@ -384,14 +382,12 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
# Check and return process' return code
if returncode is None:
if raise_on_error:
raise MoulinetteError(
errno.EIO, m18n.n('hook_exec_not_terminated', path=path))
raise YunohostError('hook_exec_not_terminated', path=path)
else:
logger.error(m18n.n('hook_exec_not_terminated', path=path))
return 1
elif raise_on_error and returncode != 0:
raise MoulinetteError(
errno.EIO, m18n.n('hook_exec_failed', path=path))
raise YunohostError('hook_exec_failed', path=path)
return returncode

View file

@ -26,15 +26,13 @@
import os
import yaml
import errno
import collections
from datetime import datetime
from logging import FileHandler, getLogger, Formatter
from sys import exc_info
from moulinette import m18n, msettings
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_file
@ -148,8 +146,7 @@ def log_display(path, number=50, share=False):
log_path = base_path + LOG_FILE_EXT
if not os.path.exists(md_path) and not os.path.exists(log_path):
raise MoulinetteError(errno.EINVAL,
m18n.n('log_does_exists', log=path))
raise YunohostError('log_does_exists', log=path)
infos = {}
@ -189,7 +186,7 @@ def log_display(path, number=50, share=False):
if os.path.exists(log_path):
logger.warning(error)
else:
raise MoulinetteError(errno.EINVAL, error)
raise YunohostError(error)
# Display logs if exist
if os.path.exists(log_path):
@ -285,6 +282,7 @@ def is_unit_operation(entities=['app', 'domain', 'service', 'user'],
class OperationLogger(object):
"""
Instances of this class represents unit operation done on the ynh instance.
@ -316,7 +314,7 @@ class OperationLogger(object):
"""
if self.started_at is None:
self.started_at = datetime.now()
self.started_at = datetime.utcnow()
self.flush()
self._register_log()
@ -408,7 +406,7 @@ class OperationLogger(object):
return
if error is not None and not isinstance(error, basestring):
error = str(error)
self.ended_at = datetime.now()
self.ended_at = datetime.utcnow()
self._error = error
self._success = error is None
if self.logger is not None:
@ -425,7 +423,7 @@ class OperationLogger(object):
else:
if is_api:
msg = "<strong>" + m18n.n('log_link_to_failed_log',
name=self.name, desc=desc) + "</strong>"
name=self.name, desc=desc) + "</strong>"
else:
msg = m18n.n('log_help_to_get_failed_log', name=self.name,
desc=desc)

View file

@ -31,14 +31,13 @@ import calendar
import subprocess
import xmlrpclib
import os.path
import errno
import os
import dns.resolver
import cPickle as pickle
from datetime import datetime
from moulinette import m18n
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
from yunohost.utils.network import get_public_ip
@ -83,7 +82,7 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False):
result_dname = dn
if len(devices) == 0:
if mountpoint is not None:
raise MoulinetteError(errno.ENODEV, m18n.n('mountpoint_unknown'))
raise YunohostError('mountpoint_unknown')
return result
# Retrieve monitoring for unit(s)
@ -141,7 +140,7 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False):
for dname in devices_names:
_set(dname, 'not-available')
else:
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u))
raise YunohostError('unit_unknown', unit=u)
if result_dname is not None:
return result[result_dname]
@ -237,7 +236,7 @@ def monitor_network(units=None, human_readable=False):
'gateway': gateway,
}
else:
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u))
raise YunohostError('unit_unknown', unit=u)
if len(units) == 1:
return result[units[0]]
@ -287,9 +286,9 @@ def monitor_system(units=None, human_readable=False):
elif u == 'infos':
result[u] = json.loads(glances.getSystem())
else:
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u))
raise YunohostError('unit_unknown', unit=u)
if len(units) == 1 and type(result[units[0]]) is not str:
if len(units) == 1 and not isinstance(result[units[0]], str):
return result[units[0]]
return result
@ -303,7 +302,7 @@ def monitor_update_stats(period):
"""
if period not in ['day', 'week', 'month']:
raise MoulinetteError(errno.EINVAL, m18n.n('monitor_period_invalid'))
raise YunohostError('monitor_period_invalid')
stats = _retrieve_stats(period)
if not stats:
@ -321,7 +320,7 @@ def monitor_update_stats(period):
else:
monitor = _monitor_all(p, 0)
if not monitor:
raise MoulinetteError(errno.ENODATA, m18n.n('monitor_stats_no_update'))
raise YunohostError('monitor_stats_no_update')
stats['timestamp'].append(time.time())
@ -386,15 +385,13 @@ def monitor_show_stats(period, date=None):
"""
if period not in ['day', 'week', 'month']:
raise MoulinetteError(errno.EINVAL, m18n.n('monitor_period_invalid'))
raise YunohostError('monitor_period_invalid')
result = _retrieve_stats(period, date)
if result is False:
raise MoulinetteError(errno.ENOENT,
m18n.n('monitor_stats_file_not_found'))
raise YunohostError('monitor_stats_file_not_found')
elif result is None:
raise MoulinetteError(errno.EINVAL,
m18n.n('monitor_stats_period_unavailable'))
raise YunohostError('monitor_stats_period_unavailable')
return result
@ -407,7 +404,7 @@ def monitor_enable(with_stats=False):
"""
from yunohost.service import (service_status, service_enable,
service_start)
service_start)
glances = service_status('glances')
if glances['status'] != 'running':
@ -417,7 +414,7 @@ def monitor_enable(with_stats=False):
# Install crontab
if with_stats:
# day: every 5 min # week: every 1 h # month: every 4 h #
# day: every 5 min # week: every 1 h # month: every 4 h #
rules = ('*/5 * * * * root {cmd} day >> /dev/null\n'
'3 * * * * root {cmd} week >> /dev/null\n'
'6 */4 * * * root {cmd} month >> /dev/null').format(
@ -434,7 +431,7 @@ def monitor_disable():
"""
from yunohost.service import (service_status, service_disable,
service_stop)
service_stop)
glances = service_status('glances')
if glances['status'] != 'inactive':
@ -442,7 +439,7 @@ def monitor_disable():
if glances['loaded'] != 'disabled':
try:
service_disable('glances')
except MoulinetteError as e:
except YunohostError as e:
logger.warning(e.strerror)
# Remove crontab
@ -470,8 +467,8 @@ def _get_glances_api():
from yunohost.service import service_status
if service_status('glances')['status'] != 'running':
raise MoulinetteError(errno.EPERM, m18n.n('monitor_not_enabled'))
raise MoulinetteError(errno.EIO, m18n.n('monitor_glances_con_failed'))
raise YunohostError('monitor_not_enabled')
raise YunohostError('monitor_glances_con_failed')
def _extract_inet(string, skip_netmask=False, skip_loopback=True):

View file

@ -28,7 +28,6 @@ import time
import yaml
import json
import subprocess
import errno
import shutil
import hashlib
@ -36,11 +35,11 @@ from difflib import unified_diff
from datetime import datetime
from moulinette import m18n
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils import log, filesystem
from yunohost.hook import hook_callback
from yunohost.log import is_unit_operation
from yunohost.hook import hook_callback, hook_list
BASE_CONF_PATH = '/home/yunohost.conf'
BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup')
@ -85,7 +84,7 @@ def service_add(name, status=None, log=None, runlevel=None, need_lock=False, des
_save_services(services)
except:
# we'll get a logger.warning with more details in _save_services
raise MoulinetteError(errno.EIO, m18n.n('service_add_failed', service=name))
raise YunohostError('service_add_failed', service=name)
logger.success(m18n.n('service_added', service=name))
@ -103,13 +102,13 @@ def service_remove(name):
try:
del services[name]
except KeyError:
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=name))
raise YunohostError('service_unknown', service=name)
try:
_save_services(services)
except:
# we'll get a logger.warning with more details in _save_services
raise MoulinetteError(errno.EIO, m18n.n('service_remove_failed', service=name))
raise YunohostError('service_remove_failed', service=name)
logger.success(m18n.n('service_removed', service=name))
@ -130,10 +129,7 @@ def service_start(names):
logger.success(m18n.n('service_started', service=name))
else:
if service_status(name)['status'] != 'running':
raise MoulinetteError(errno.EPERM,
m18n.n('service_start_failed',
service=name,
logs=_get_journalctl_logs(name)))
raise YunohostError('service_start_failed', service=name, logs=_get_journalctl_logs(name))
logger.debug(m18n.n('service_already_started', service=name))
@ -152,12 +148,10 @@ def service_stop(names):
logger.success(m18n.n('service_stopped', service=name))
else:
if service_status(name)['status'] != 'inactive':
raise MoulinetteError(errno.EPERM,
m18n.n('service_stop_failed',
service=name,
logs=_get_journalctl_logs(name)))
raise YunohostError('service_stop_failed', service=name, logs=_get_journalctl_logs(name))
logger.debug(m18n.n('service_already_stopped', service=name))
@is_unit_operation()
def service_enable(operation_logger, names):
"""
@ -174,10 +168,7 @@ def service_enable(operation_logger, names):
if _run_service_command('enable', name):
logger.success(m18n.n('service_enabled', service=name))
else:
raise MoulinetteError(errno.EPERM,
m18n.n('service_enable_failed',
service=name,
logs=_get_journalctl_logs(name)))
raise YunohostError('service_enable_failed', service=name, logs=_get_journalctl_logs(name))
def service_disable(names):
@ -194,10 +185,7 @@ def service_disable(names):
if _run_service_command('disable', name):
logger.success(m18n.n('service_disabled', service=name))
else:
raise MoulinetteError(errno.EPERM,
m18n.n('service_disable_failed',
service=name,
logs=_get_journalctl_logs(name)))
raise YunohostError('service_disable_failed', service=name, logs=_get_journalctl_logs(name))
def service_status(names=[]):
@ -220,8 +208,7 @@ def service_status(names=[]):
for name in names:
if check_names and name not in services.keys():
raise MoulinetteError(errno.EINVAL,
m18n.n('service_unknown', service=name))
raise YunohostError('service_unknown', service=name)
# this "service" isn't a service actually so we skip it
#
@ -248,10 +235,7 @@ def service_status(names=[]):
'status': "unknown",
'loaded': "unknown",
'active': "unknown",
'active_at': {
"timestamp": "unknown",
"human": "unknown",
},
'active_at': "unknown",
'description': "Error: failed to get information for this service, it doesn't exists for systemd",
'service_file_path': "unknown",
}
@ -273,13 +257,13 @@ def service_status(names=[]):
'status': str(status.get("SubState", "unknown")),
'loaded': "enabled" if str(status.get("LoadState", "unknown")) == "loaded" else str(status.get("LoadState", "unknown")),
'active': str(status.get("ActiveState", "unknown")),
'active_at': {
"timestamp": str(status.get("ActiveEnterTimestamp", "unknown")),
"human": datetime.fromtimestamp(status["ActiveEnterTimestamp"] / 1000000).strftime("%F %X") if "ActiveEnterTimestamp" in status else "unknown",
},
'description': description,
'service_file_path': str(status.get("FragmentPath", "unknown")),
}
if "ActiveEnterTimestamp" in status:
result[name]['active_at'] = datetime.utcfromtimestamp(status["ActiveEnterTimestamp"] / 1000000)
else:
result[name]['active_at'] = "unknown"
if len(names) == 1:
return result[names[0]]
@ -293,7 +277,7 @@ def _get_service_information_from_systemd(service):
d = dbus.SystemBus()
systemd = d.get_object('org.freedesktop.systemd1','/org/freedesktop/systemd1')
systemd = d.get_object('org.freedesktop.systemd1', '/org/freedesktop/systemd1')
manager = dbus.Interface(systemd, 'org.freedesktop.systemd1.Manager')
try:
@ -323,10 +307,10 @@ def service_log(name, number=50):
services = _get_services()
if name not in services.keys():
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=name))
raise YunohostError('service_unknown', service=name)
if 'log' not in services[name]:
raise MoulinetteError(errno.EPERM, m18n.n('service_no_log', service=name))
raise YunohostError('service_no_log', service=name)
log_list = services[name]['log']
@ -394,7 +378,7 @@ def service_regen_conf(operation_logger, names=[], with_diff=False, force=False,
if not names:
operation_logger.name_parameter_override = 'all'
elif len(names) != 1:
operation_logger.name_parameter_override = str(len(operation_logger.related_to))+'_services'
operation_logger.name_parameter_override = str(len(operation_logger.related_to)) + '_services'
operation_logger.start()
# Clean pending conf directory
@ -406,7 +390,7 @@ def service_regen_conf(operation_logger, names=[], with_diff=False, force=False,
shutil.rmtree(os.path.join(PENDING_CONF_DIR, name),
ignore_errors=True)
else:
filesystem.mkdir(PENDING_CONF_DIR, 0755, True)
filesystem.mkdir(PENDING_CONF_DIR, 0o755, True)
# Format common hooks arguments
common_args = [1 if force else 0, 1 if dry_run else 0]
@ -417,20 +401,25 @@ def service_regen_conf(operation_logger, names=[], with_diff=False, force=False,
def _pre_call(name, priority, path, args):
# create the pending conf directory for the service
service_pending_path = os.path.join(PENDING_CONF_DIR, name)
filesystem.mkdir(service_pending_path, 0755, True, uid='root')
filesystem.mkdir(service_pending_path, 0o755, True, uid='root')
# return the arguments to pass to the script
return pre_args + [service_pending_path, ]
# Don't regen SSH if not specifically specified
if not names:
names = hook_list('conf_regen', list_by='name',
show_info=False)['hooks']
names.remove('ssh')
pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call)
# Update the services name
names = pre_result['succeed'].keys()
if not names:
raise MoulinetteError(errno.EIO,
m18n.n('service_regenconf_failed',
services=', '.join(pre_result['failed'])))
raise YunohostError('service_regenconf_failed',
services=', '.join(pre_result['failed']))
# Set the processing method
_regen = _process_regen_conf if not dry_run else lambda *a, **k: True
@ -502,8 +491,8 @@ def service_regen_conf(operation_logger, names=[], with_diff=False, force=False,
# we assume that it is safe to regen it, since the file is backuped
# anyway (by default in _regen), as long as we warn the user
# appropriately.
logger.info(m18n.n('service_conf_new_managed_file',
conf=system_path, service=service))
logger.info(m18n.n('service_conf_now_managed_by_yunohost',
conf=system_path))
regenerated = _regen(system_path, pending_path)
conf_status = 'new'
elif force:
@ -606,7 +595,7 @@ def _run_service_command(action, service):
"""
services = _get_services()
if service not in services.keys():
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=service))
raise YunohostError('service_unknown', service=service)
possible_actions = ['start', 'stop', 'restart', 'reload', 'enable', 'disable']
if action not in possible_actions:
@ -615,7 +604,7 @@ def _run_service_command(action, service):
cmd = 'systemctl %s %s' % (action, service)
need_lock = services[service].get('need_lock', False) \
and action in ['start', 'stop', 'restart', 'reload']
and action in ['start', 'stop', 'restart', 'reload']
try:
# Launch the command
@ -649,10 +638,10 @@ def _give_lock(action, service, p):
else:
systemctl_PID_name = "ControlPID"
cmd_get_son_PID ="systemctl show %s -p %s" % (service, systemctl_PID_name)
cmd_get_son_PID = "systemctl show %s -p %s" % (service, systemctl_PID_name)
son_PID = 0
# As long as we did not found the PID and that the command is still running
while son_PID == 0 and p.poll() == None:
while son_PID == 0 and p.poll() is None:
# Call systemctl to get the PID
# Output of the command is e.g. ControlPID=1234
son_PID = subprocess.check_output(cmd_get_son_PID.split()) \
@ -669,11 +658,12 @@ def _give_lock(action, service, p):
return son_PID
def _remove_lock(PID_to_remove):
# FIXME ironically not concurrency safe because it's not atomic...
PIDs = filesystem.read_file(MOULINETTE_LOCK).split("\n")
PIDs_to_keep = [ PID for PID in PIDs if int(PID) != PID_to_remove ]
PIDs_to_keep = [PID for PID in PIDs if int(PID) != PID_to_remove]
filesystem.write_to_file(MOULINETTE_LOCK, '\n'.join(PIDs_to_keep))
@ -787,6 +777,7 @@ def _find_previous_log_file(file):
return None
def _get_files_diff(orig_file, new_file, as_string=False, skip_header=True):
"""Compare two files and return the differences
@ -927,26 +918,26 @@ def _process_regen_conf(system_conf, new_conf=None, save=True):
"""
if save:
backup_path = os.path.join(BACKUP_CONF_DIR, '{0}-{1}'.format(
system_conf.lstrip('/'), time.strftime("%Y%m%d.%H%M%S")))
system_conf.lstrip('/'), datetime.utcnow().strftime("%Y%m%d.%H%M%S")))
backup_dir = os.path.dirname(backup_path)
if not os.path.isdir(backup_dir):
filesystem.mkdir(backup_dir, 0755, True)
filesystem.mkdir(backup_dir, 0o755, True)
shutil.copy2(system_conf, backup_path)
logger.debug(m18n.n('service_conf_file_backed_up',
conf=system_conf, backup=backup_path))
conf=system_conf, backup=backup_path))
try:
if not new_conf:
os.remove(system_conf)
logger.debug(m18n.n('service_conf_file_removed',
conf=system_conf))
conf=system_conf))
else:
system_dir = os.path.dirname(system_conf)
if not os.path.isdir(system_dir):
filesystem.mkdir(system_dir, 0755, True)
filesystem.mkdir(system_dir, 0o755, True)
shutil.copyfile(new_conf, system_conf)
logger.debug(m18n.n('service_conf_file_updated',

View file

@ -1,12 +1,11 @@
import os
import json
import errno
from datetime import datetime
from collections import OrderedDict
from moulinette import m18n
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
logger = getActionLogger('yunohost.settings')
@ -39,6 +38,7 @@ DEFAULTS = OrderedDict([
# -1 disabled, 0 alert if listed, 1 8-letter, 2 normal, 3 strong, 4 strongest
("security.password.admin.strength", {"type": "int", "default": 1}),
("security.password.user.strength", {"type": "int", "default": 1}),
("service.ssh.allow_deprecated_dsa_hostkey", {"type": "bool", "default": False}),
])
@ -53,8 +53,7 @@ def settings_get(key, full=False):
settings = _get_settings()
if key not in settings:
raise MoulinetteError(errno.EINVAL, m18n.n(
'global_settings_key_doesnt_exists', settings_key=key))
raise YunohostError('global_settings_key_doesnt_exists', settings_key=key)
if full:
return settings[key]
@ -82,39 +81,39 @@ def settings_set(key, value):
settings = _get_settings()
if key not in settings:
raise MoulinetteError(errno.EINVAL, m18n.n(
'global_settings_key_doesnt_exists', settings_key=key))
raise YunohostError('global_settings_key_doesnt_exists', settings_key=key)
key_type = settings[key]["type"]
if key_type == "bool":
if not isinstance(value, bool):
raise MoulinetteError(errno.EINVAL, m18n.n(
'global_settings_bad_type_for_setting', setting=key,
received_type=type(value).__name__, expected_type=key_type))
raise YunohostError('global_settings_bad_type_for_setting', setting=key,
received_type=type(value).__name__, expected_type=key_type)
elif key_type == "int":
if not isinstance(value, int) or isinstance(value, bool):
if isinstance(value, str):
value=int(value)
try:
value = int(value)
except:
raise YunohostError('global_settings_bad_type_for_setting',
setting=key,
received_type=type(value).__name__,
expected_type=key_type)
else:
raise MoulinetteError(errno.EINVAL, m18n.n(
'global_settings_bad_type_for_setting', setting=key,
received_type=type(value).__name__, expected_type=key_type))
raise YunohostError('global_settings_bad_type_for_setting', setting=key,
received_type=type(value).__name__, expected_type=key_type)
elif key_type == "string":
if not isinstance(value, basestring):
raise MoulinetteError(errno.EINVAL, m18n.n(
'global_settings_bad_type_for_setting', setting=key,
received_type=type(value).__name__, expected_type=key_type))
raise YunohostError('global_settings_bad_type_for_setting', setting=key,
received_type=type(value).__name__, expected_type=key_type)
elif key_type == "enum":
if value not in settings[key]["choices"]:
raise MoulinetteError(errno.EINVAL, m18n.n(
'global_settings_bad_choice_for_enum', setting=key,
received_type=type(value).__name__,
expected_type=", ".join(settings[key]["choices"])))
raise YunohostError('global_settings_bad_choice_for_enum', setting=key,
received_type=type(value).__name__,
expected_type=", ".join(settings[key]["choices"]))
else:
raise MoulinetteError(errno.EINVAL, m18n.n(
'global_settings_unknown_type', setting=key,
unknown_type=key_type))
raise YunohostError('global_settings_unknown_type', setting=key,
unknown_type=key_type)
settings[key]["value"] = value
@ -132,8 +131,7 @@ def settings_reset(key):
settings = _get_settings()
if key not in settings:
raise MoulinetteError(errno.EINVAL, m18n.n(
'global_settings_key_doesnt_exists', settings_key=key))
raise YunohostError('global_settings_key_doesnt_exists', settings_key=key)
settings[key]["value"] = settings[key]["default"]
_save_settings(settings)
@ -154,7 +152,7 @@ def settings_reset_all():
# addition but we'll see if this is a common need.
# Another solution would be to use etckeeper and integrate those
# modification inside of it and take advantage of its git history
old_settings_backup_path = SETTINGS_PATH_OTHER_LOCATION % datetime.now().strftime("%F_%X")
old_settings_backup_path = SETTINGS_PATH_OTHER_LOCATION % datetime.utcnow().strftime("%F_%X")
_save_settings(settings, location=old_settings_backup_path)
for value in settings.values():
@ -209,8 +207,7 @@ def _get_settings():
setting_key=key))
unknown_settings[key] = value
except Exception as e:
raise MoulinetteError(errno.EIO, m18n.n('global_settings_cant_open_settings', reason=e),
exc_info=1)
raise YunohostError('global_settings_cant_open_settings', reason=e)
if unknown_settings:
try:
@ -231,14 +228,10 @@ def _save_settings(settings, location=SETTINGS_PATH):
try:
result = json.dumps(settings_without_description, indent=4)
except Exception as e:
raise MoulinetteError(errno.EINVAL,
m18n.n('global_settings_cant_serialize_settings', reason=e),
exc_info=1)
raise YunohostError('global_settings_cant_serialize_settings', reason=e)
try:
with open(location, "w") as settings_fd:
settings_fd.write(result)
except Exception as e:
raise MoulinetteError(errno.EIO,
m18n.n('global_settings_cant_write_settings', reason=e),
exc_info=1)
raise YunohostError('global_settings_cant_write_settings', reason=e)

View file

@ -2,12 +2,10 @@
import re
import os
import errno
import pwd
import subprocess
from moulinette import m18n
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils.filesystem import read_file, write_to_file, chown, chmod, mkdir
SSHD_CONFIG_PATH = "/etc/ssh/sshd_config"
@ -23,7 +21,7 @@ def user_ssh_allow(auth, username):
# TODO it would be good to support different kind of shells
if not _get_user_for_ssh(auth, username):
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
raise YunohostError('user_unknown', user=username)
auth.update('uid=%s,ou=users' % username, {'loginShell': '/bin/bash'})
@ -42,7 +40,7 @@ def user_ssh_disallow(auth, username):
# TODO it would be good to support different kind of shells
if not _get_user_for_ssh(auth, username):
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
raise YunohostError('user_unknown', user=username)
auth.update('uid=%s,ou=users' % username, {'loginShell': '/bin/false'})
@ -99,7 +97,7 @@ def user_ssh_add_key(auth, username, key, comment):
# create empty file to set good permissions
write_to_file(authorized_keys_file, "")
chown(authorized_keys_file, uid=user["uid"][0])
chmod(authorized_keys_file, 0600)
chmod(authorized_keys_file, 0o600)
authorized_keys_content = read_file(authorized_keys_file)

View file

@ -7,12 +7,14 @@ sys.path.append("..")
def pytest_addoption(parser):
parser.addoption("--yunodebug", action="store_true", default=False)
###############################################################################
# Tweak translator to raise exceptions if string keys are not defined #
###############################################################################
#
# Tweak translator to raise exceptions if string keys are not defined #
#
old_translate = moulinette.core.Translator.translate
def new_translate(self, key, *args, **kwargs):
if key not in self._translations[self.default_locale].keys():
@ -21,14 +23,15 @@ def new_translate(self, key, *args, **kwargs):
return old_translate(self, key, *args, **kwargs)
moulinette.core.Translator.translate = new_translate
def new_m18nn(self, key, *args, **kwargs):
return self._namespaces[self._current_namespace].translate(key, *args, **kwargs)
moulinette.core.Moulinette18n.n = new_m18nn
###############################################################################
# Init the moulinette to have the cli loggers stuff #
###############################################################################
#
# Init the moulinette to have the cli loggers stuff #
#
def pytest_cmdline_main(config):

View file

@ -5,7 +5,7 @@ import requests_mock
import glob
import time
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from yunohost.app import app_fetchlist, app_removelist, app_listlists, _using_legacy_appslist_system, _migrate_appslist_system, _register_new_appslist
@ -17,7 +17,7 @@ APPSLISTS_JSON = '/etc/yunohost/appslists.json'
def setup_function(function):
# Clear all appslist
files = glob.glob(REPO_PATH+"/*")
files = glob.glob(REPO_PATH + "/*")
for f in files:
os.remove(f)
@ -42,9 +42,9 @@ def cron_job_is_there():
return r == 0
###############################################################################
# Test listing of appslists and registering of appslists #
###############################################################################
#
# Test listing of appslists and registering of appslists #
#
def test_appslist_list_empty():
@ -79,7 +79,7 @@ def test_appslist_list_register_conflict_name():
"""
_register_new_appslist("https://lol.com/appslist.json", "dummy")
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
_register_new_appslist("https://lol.com/appslist2.json", "dummy")
appslist_dict = app_listlists()
@ -94,7 +94,7 @@ def test_appslist_list_register_conflict_url():
"""
_register_new_appslist("https://lol.com/appslist.json", "dummy")
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
_register_new_appslist("https://lol.com/appslist.json", "plopette")
appslist_dict = app_listlists()
@ -103,9 +103,9 @@ def test_appslist_list_register_conflict_url():
assert "plopette" not in appslist_dict.keys()
###############################################################################
# Test fetching of appslists #
###############################################################################
#
# Test fetching of appslists #
#
def test_appslist_fetch():
@ -161,7 +161,7 @@ def test_appslist_fetch_unknownlist():
assert app_listlists() == {}
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
app_fetchlist(name="swag")
@ -170,7 +170,7 @@ def test_appslist_fetch_url_but_no_name():
Do a fetchlist with url given, but no name given
"""
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
app_fetchlist(url=URL_OFFICIAL_APP_LIST)
@ -244,9 +244,9 @@ def test_appslist_fetch_timeout():
app_fetchlist()
###############################################################################
# Test remove of appslist #
###############################################################################
#
# Test remove of appslist #
#
def test_appslist_remove():
@ -270,13 +270,13 @@ def test_appslist_remove_unknown():
Attempt to remove an unknown list
"""
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
app_removelist("dummy")
###############################################################################
# Test migration from legacy appslist system #
###############################################################################
#
# Test migration from legacy appslist system #
#
def add_legacy_cron(name, url):

View file

@ -1,7 +1,7 @@
import pytest
from moulinette.core import MoulinetteError, init_authenticator
from moulinette.core import init_authenticator
from yunohost.utils.error import YunohostError
from yunohost.app import app_install, app_remove
from yunohost.domain import _get_maindomain, domain_url_available, _normalize_domain_path
@ -22,6 +22,7 @@ def setup_function(function):
except:
pass
def teardown_function(function):
try:
@ -43,25 +44,25 @@ def test_urlavailable():
assert domain_url_available(auth, maindomain, "/macnuggets")
# We don't know the domain yolo.swag
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
assert domain_url_available(auth, "yolo.swag", "/macnuggets")
def test_registerurl():
app_install(auth, "./tests/apps/register_url_app_ynh",
args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"))
args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"))
assert not domain_url_available(auth, maindomain, "/urlregisterapp")
# Try installing at same location
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
app_install(auth, "./tests/apps/register_url_app_ynh",
args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"))
args="domain=%s&path=%s" % (maindomain, "/urlregisterapp"))
def test_registerurl_baddomain():
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
app_install(auth, "./tests/apps/register_url_app_ynh",
args="domain=%s&path=%s" % ("yolo.swag", "/urlregisterapp"))
args="domain=%s&path=%s" % ("yolo.swag", "/urlregisterapp"))

View file

@ -12,18 +12,22 @@ from yunohost.app import app_install, app_remove, app_ssowatconf
from yunohost.app import _is_installed
from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete
from yunohost.domain import _get_maindomain
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
# Get main domain
maindomain = _get_maindomain()
maindomain = ""
# Instantiate LDAP Authenticator
AUTH_IDENTIFIER = ('ldap', 'ldap-anonymous')
AUTH_PARAMETERS = {'uri': 'ldap://localhost:389', 'base_dn': 'dc=yunohost,dc=org'}
auth = None
def setup_function(function):
global maindomain
maindomain = _get_maindomain()
print ""
global auth
@ -84,9 +88,9 @@ def teardown_function(function):
shutil.rmtree("/opt/test_backup_output_directory")
###############################################################################
# Helpers #
###############################################################################
#
# Helpers #
#
def app_is_installed(app):
@ -108,6 +112,7 @@ def backup_test_dependencies_are_met():
return True
def tmp_backup_directory_is_empty():
if not os.path.exists("/home/yunohost.backup/tmp/"):
@ -115,6 +120,7 @@ def tmp_backup_directory_is_empty():
else:
return len(os.listdir('/home/yunohost.backup/tmp/')) == 0
def clean_tmp_backup_directory():
if tmp_backup_directory_is_empty():
@ -122,10 +128,10 @@ def clean_tmp_backup_directory():
mount_lines = subprocess.check_output("mount").split("\n")
points_to_umount = [ line.split(" ")[2]
for line in mount_lines
if len(line) >= 3
and line.split(" ")[2].startswith("/home/yunohost.backup/tmp") ]
points_to_umount = [line.split(" ")[2]
for line in mount_lines
if len(line) >= 3
and line.split(" ")[2].startswith("/home/yunohost.backup/tmp")]
for point in reversed(points_to_umount):
os.system("umount %s" % point)
@ -135,6 +141,7 @@ def clean_tmp_backup_directory():
shutil.rmtree("/home/yunohost.backup/tmp/")
def reset_ssowat_conf():
# Make sure we have a ssowat
@ -188,14 +195,15 @@ def add_archive_system_from_2p4():
os.system("cp ./tests/apps/backup_system_from_2p4/backup.tar.gz \
/home/yunohost.backup/archives/backup_system_from_2p4.tar.gz")
###############################################################################
# System backup #
###############################################################################
#
# System backup #
#
def test_backup_only_ldap():
# Create the backup
backup_create(ignore_system=False, ignore_apps=True, system=["conf_ldap"])
backup_create(system=["conf_ldap"], apps=None)
archives = backup_list()["archives"]
assert len(archives) == 1
@ -211,20 +219,21 @@ def test_backup_system_part_that_does_not_exists(mocker):
mocker.spy(m18n, "n")
# Create the backup
with pytest.raises(MoulinetteError):
backup_create(ignore_system=False, ignore_apps=True, system=["yolol"])
with pytest.raises(YunohostError):
backup_create(system=["yolol"], apps=None)
m18n.n.assert_any_call('backup_hook_unknown', hook="yolol")
m18n.n.assert_any_call('backup_nothings_done')
###############################################################################
# System backup and restore #
###############################################################################
#
# System backup and restore #
#
def test_backup_and_restore_all_sys():
# Create the backup
backup_create(ignore_system=False, ignore_apps=True)
backup_create(system=[], apps=None)
archives = backup_list()["archives"]
assert len(archives) == 1
@ -241,40 +250,41 @@ def test_backup_and_restore_all_sys():
# Restore the backup
backup_restore(auth, name=archives[0], force=True,
ignore_system=False, ignore_apps=True)
system=[], apps=None)
# Check ssowat conf is back
assert os.path.exists("/etc/ssowat/conf.json")
###############################################################################
# System restore from 2.4 #
###############################################################################
#
# System restore from 2.4 #
#
@pytest.mark.with_system_archive_from_2p4
def test_restore_system_from_Ynh2p4(monkeypatch, mocker):
# Backup current system
backup_create(ignore_system=False, ignore_apps=True)
backup_create(system=[], apps=None)
archives = backup_list()["archives"]
assert len(archives) == 2
# Restore system archive from 2.4
try:
backup_restore(auth, name=backup_list()["archives"][1],
ignore_system=False,
ignore_apps=True,
force=True)
system=[],
apps=None,
force=True)
finally:
# Restore system as it was
backup_restore(auth, name=backup_list()["archives"][0],
ignore_system=False,
ignore_apps=True,
force=True)
system=[],
apps=None,
force=True)
#
# App backup #
#
###############################################################################
# App backup #
###############################################################################
@pytest.mark.with_backup_recommended_app_installed
def test_backup_script_failure_handling(monkeypatch, mocker):
@ -292,11 +302,12 @@ def test_backup_script_failure_handling(monkeypatch, mocker):
monkeypatch.setattr("yunohost.backup.hook_exec", custom_hook_exec)
mocker.spy(m18n, "n")
with pytest.raises(MoulinetteError):
backup_create(ignore_system=True, ignore_apps=False, apps=["backup_recommended_app"])
with pytest.raises(YunohostError):
backup_create(system=None, apps=["backup_recommended_app"])
m18n.n.assert_any_call('backup_app_failed', app='backup_recommended_app')
@pytest.mark.with_backup_recommended_app_installed
def test_backup_not_enough_free_space(monkeypatch, mocker):
@ -312,8 +323,8 @@ def test_backup_not_enough_free_space(monkeypatch, mocker):
mocker.spy(m18n, "n")
with pytest.raises(MoulinetteError):
backup_create(ignore_system=True, ignore_apps=False, apps=["backup_recommended_app"])
with pytest.raises(YunohostError):
backup_create(system=None, apps=["backup_recommended_app"])
m18n.n.assert_any_call('not_enough_disk_space', path=ANY)
@ -324,8 +335,8 @@ def test_backup_app_not_installed(mocker):
mocker.spy(m18n, "n")
with pytest.raises(MoulinetteError):
backup_create(ignore_system=True, ignore_apps=False, apps=["wordpress"])
with pytest.raises(YunohostError):
backup_create(system=None, apps=["wordpress"])
m18n.n.assert_any_call("unbackup_app", app="wordpress")
m18n.n.assert_any_call('backup_nothings_done')
@ -340,8 +351,8 @@ def test_backup_app_with_no_backup_script(mocker):
mocker.spy(m18n, "n")
with pytest.raises(MoulinetteError):
backup_create(ignore_system=True, ignore_apps=False, apps=["backup_recommended_app"])
with pytest.raises(YunohostError):
backup_create(system=None, apps=["backup_recommended_app"])
m18n.n.assert_any_call("backup_with_no_backup_script_for_app", app="backup_recommended_app")
m18n.n.assert_any_call('backup_nothings_done')
@ -359,7 +370,7 @@ def test_backup_app_with_no_restore_script(mocker):
# Backuping an app with no restore script will only display a warning to the
# user...
backup_create(ignore_system=True, ignore_apps=False, apps=["backup_recommended_app"])
backup_create(system=None, apps=["backup_recommended_app"])
m18n.n.assert_any_call("backup_with_no_restore_script_for_app", app="backup_recommended_app")
@ -368,7 +379,7 @@ def test_backup_app_with_no_restore_script(mocker):
def test_backup_with_different_output_directory():
# Create the backup
backup_create(ignore_system=False, ignore_apps=True, system=["conf_ssh"],
backup_create(system=["conf_ssh"], apps=None,
output_directory="/opt/test_backup_output_directory",
name="backup")
@ -382,10 +393,11 @@ def test_backup_with_different_output_directory():
assert len(archives_info["system"].keys()) == 1
assert "conf_ssh" in archives_info["system"].keys()
@pytest.mark.clean_opt_dir
def test_backup_with_no_compress():
# Create the backup
backup_create(ignore_system=False, ignore_apps=True, system=["conf_nginx"],
backup_create(system=["conf_nginx"], apps=None,
output_directory="/opt/test_backup_output_directory",
no_compress=True,
name="backup")
@ -393,17 +405,15 @@ def test_backup_with_no_compress():
assert os.path.exists("/opt/test_backup_output_directory/info.json")
###############################################################################
# App restore #
###############################################################################
#
# App restore #
#
@pytest.mark.with_wordpress_archive_from_2p4
def test_restore_app_wordpress_from_Ynh2p4():
backup_restore(auth, name=backup_list()["archives"][0],
ignore_system=True,
ignore_apps=False,
apps=["wordpress"])
backup_restore(auth, system=None, name=backup_list()["archives"][0],
apps=["wordpress"])
@pytest.mark.with_wordpress_archive_from_2p4
@ -419,11 +429,9 @@ def test_restore_app_script_failure_handling(monkeypatch, mocker):
assert not _is_installed("wordpress")
with pytest.raises(MoulinetteError):
backup_restore(auth, name=backup_list()["archives"][0],
ignore_system=True,
ignore_apps=False,
apps=["wordpress"])
with pytest.raises(YunohostError):
backup_restore(auth, system=None, name=backup_list()["archives"][0],
apps=["wordpress"])
m18n.n.assert_any_call('restore_app_failed', app='wordpress')
m18n.n.assert_any_call('restore_nothings_done')
@ -442,16 +450,14 @@ def test_restore_app_not_enough_free_space(monkeypatch, mocker):
assert not _is_installed("wordpress")
with pytest.raises(MoulinetteError):
backup_restore(auth, name=backup_list()["archives"][0],
ignore_system=True,
ignore_apps=False,
apps=["wordpress"])
with pytest.raises(YunohostError):
backup_restore(auth, system=None, name=backup_list()["archives"][0],
apps=["wordpress"])
m18n.n.assert_any_call('restore_not_enough_disk_space',
free_space=0,
margin=ANY,
needed_space=ANY)
free_space=0,
margin=ANY,
needed_space=ANY)
assert not _is_installed("wordpress")
@ -463,11 +469,9 @@ def test_restore_app_not_in_backup(mocker):
mocker.spy(m18n, "n")
with pytest.raises(MoulinetteError):
backup_restore(auth, name=backup_list()["archives"][0],
ignore_system=True,
ignore_apps=False,
apps=["yoloswag"])
with pytest.raises(YunohostError):
backup_restore(auth, system=None, name=backup_list()["archives"][0],
apps=["yoloswag"])
m18n.n.assert_any_call('backup_archive_app_not_found', app="yoloswag")
assert not _is_installed("wordpress")
@ -479,19 +483,15 @@ def test_restore_app_already_installed(mocker):
assert not _is_installed("wordpress")
backup_restore(auth, name=backup_list()["archives"][0],
ignore_system=True,
ignore_apps=False,
apps=["wordpress"])
backup_restore(auth, system=None, name=backup_list()["archives"][0],
apps=["wordpress"])
assert _is_installed("wordpress")
mocker.spy(m18n, "n")
with pytest.raises(MoulinetteError):
backup_restore(auth, name=backup_list()["archives"][0],
ignore_system=True,
ignore_apps=False,
apps=["wordpress"])
with pytest.raises(YunohostError):
backup_restore(auth, system=None, name=backup_list()["archives"][0],
apps=["wordpress"])
m18n.n.assert_any_call('restore_already_installed_app', app="wordpress")
m18n.n.assert_any_call('restore_nothings_done')
@ -520,7 +520,7 @@ def test_backup_and_restore_with_ynh_restore():
def _test_backup_and_restore_app(app):
# Create a backup of this app
backup_create(ignore_system=True, ignore_apps=False, apps=[app])
backup_create(system=None, apps=[app])
archives = backup_list()["archives"]
assert len(archives) == 1
@ -535,14 +535,15 @@ def _test_backup_and_restore_app(app):
assert not app_is_installed(app)
# Restore the app
backup_restore(auth, name=archives[0], ignore_system=True,
ignore_apps=False, apps=[app])
backup_restore(auth, system=None, name=archives[0],
apps=[app])
assert app_is_installed(app)
###############################################################################
# Some edge cases #
###############################################################################
#
# Some edge cases #
#
def test_restore_archive_with_no_json(mocker):
@ -553,9 +554,8 @@ def test_restore_archive_with_no_json(mocker):
assert "badbackup" in backup_list()["archives"]
mocker.spy(m18n, "n")
with pytest.raises(MoulinetteError):
backup_restore(auth, name="badbackup", force=True,
ignore_system=False, ignore_apps=False)
with pytest.raises(YunohostError):
backup_restore(auth, name="badbackup", force=True)
m18n.n.assert_any_call('backup_invalid_archive')
@ -567,7 +567,7 @@ def test_backup_binds_are_readonly(monkeypatch):
confssh = os.path.join(self.work_dir, "conf/ssh")
output = subprocess.check_output("touch %s/test 2>&1 || true" % confssh,
shell=True)
shell=True, env={'LANG': 'en_US.UTF-8'})
assert "Read-only file system" in output
@ -577,7 +577,7 @@ def test_backup_binds_are_readonly(monkeypatch):
self.clean()
monkeypatch.setattr("yunohost.backup.BackupMethod.mount_and_backup",
custom_mount_and_backup)
custom_mount_and_backup)
# Create the backup
backup_create(ignore_system=False, ignore_apps=True)
backup_create(system=[])

View file

@ -6,7 +6,7 @@ from moulinette.core import init_authenticator
from yunohost.app import app_install, app_change_url, app_remove, app_map
from yunohost.domain import _get_maindomain
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
# Instantiate LDAP Authenticator
AUTH_IDENTIFIER = ('ldap', 'ldap-anonymous')
@ -38,7 +38,7 @@ def check_changeurl_app(path):
assert appmap[maindomain][path + "/"]["id"] == "change_url_app"
r = requests.get("https://%s%s/" % (maindomain, path), verify=False)
r = requests.get("https://127.0.0.1%s/" % path, headers={"domain": maindomain}, verify=False)
assert r.status_code == 200
@ -53,9 +53,10 @@ def test_appchangeurl():
check_changeurl_app("/newchangeurl")
def test_appchangeurl_sameurl():
install_changeurl_app("/changeurl")
check_changeurl_app("/changeurl")
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
app_change_url(auth, "change_url_app", maindomain, "changeurl")

View file

@ -2,7 +2,7 @@ import os
import json
import pytest
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from yunohost.settings import settings_get, settings_list, _get_settings, \
settings_set, settings_reset, settings_reset_all, \
@ -18,7 +18,8 @@ def teardown_function(function):
def test_settings_get_bool():
assert settings_get("example.bool") == True
assert settings_get("example.bool")
def test_settings_get_full_bool():
assert settings_get("example.bool", True) == {"type": "bool", "value": True, "default": True, "description": "Example boolean option"}
@ -27,6 +28,7 @@ def test_settings_get_full_bool():
def test_settings_get_int():
assert settings_get("example.int") == 42
def test_settings_get_full_int():
assert settings_get("example.int", True) == {"type": "int", "value": 42, "default": 42, "description": "Example int option"}
@ -34,6 +36,7 @@ def test_settings_get_full_int():
def test_settings_get_string():
assert settings_get("example.string") == "yolo swag"
def test_settings_get_full_string():
assert settings_get("example.string", True) == {"type": "string", "value": "yolo swag", "default": "yolo swag", "description": "Example string option"}
@ -41,12 +44,13 @@ def test_settings_get_full_string():
def test_settings_get_enum():
assert settings_get("example.enum") == "a"
def test_settings_get_full_enum():
assert settings_get("example.enum", True) == {"type": "enum", "value": "a", "default": "a", "description": "Example enum option", "choices": ["a", "b", "c"]}
def test_settings_get_doesnt_exists():
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
settings_get("doesnt.exists")
@ -70,39 +74,39 @@ def test_settings_set_enum():
def test_settings_set_doesexit():
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
settings_set("doesnt.exist", True)
def test_settings_set_bad_type_bool():
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
settings_set("example.bool", 42)
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
settings_set("example.bool", "pouet")
def test_settings_set_bad_type_int():
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
settings_set("example.int", True)
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
settings_set("example.int", "pouet")
def test_settings_set_bad_type_string():
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
settings_set("example.string", True)
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
settings_set("example.string", 42)
def test_settings_set_bad_value_enum():
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
settings_set("example.enum", True)
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
settings_set("example.enum", "e")
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
settings_set("example.enum", 42)
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
settings_set("example.enum", "pouet")
@ -119,7 +123,7 @@ def test_reset():
def test_settings_reset_doesexit():
with pytest.raises(MoulinetteError):
with pytest.raises(YunohostError):
settings_reset("doesnt.exist")
@ -152,7 +156,6 @@ def test_reset_all_backup():
assert settings_after_modification == json.load(open(old_settings_backup_path, "r"))
def test_unknown_keys():
unknown_settings_path = SETTINGS_PATH_OTHER_LOCATION % "unknown"
unknown_setting = {

View file

@ -27,8 +27,6 @@ import re
import os
import yaml
import json
import errno
import logging
import subprocess
import pwd
import socket
@ -40,7 +38,8 @@ import apt
import apt.progress
from moulinette import msettings, msignals, m18n
from moulinette.core import MoulinetteError, init_authenticator
from moulinette.core import init_authenticator
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
from moulinette.utils.process import check_output
from moulinette.utils.filesystem import read_json, write_to_json
@ -112,7 +111,7 @@ def tools_ldapinit():
pwd.getpwnam("admin")
except KeyError:
logger.error(m18n.n('ldap_init_failed_to_create_admin'))
raise MoulinetteError(errno.EINVAL, m18n.n('installation_failed'))
raise YunohostError('installation_failed')
logger.success(m18n.n('ldap_initialized'))
return auth
@ -136,11 +135,10 @@ def tools_adminpw(auth, new_password, check_strength=True):
new_hash = _hash_user_password(new_password)
try:
auth.update("cn=admin", { "userPassword": new_hash, })
auth.update("cn=admin", {"userPassword": new_hash, })
except:
logger.exception('unable to change admin password')
raise MoulinetteError(errno.EPERM,
m18n.n('admin_password_change_failed'))
raise YunohostError('admin_password_change_failed')
else:
# Write as root password
try:
@ -152,9 +150,11 @@ def tools_adminpw(auth, new_password, check_strength=True):
with open('/etc/shadow', 'w') as after_file:
after_file.write(before.replace("root:" + hash_root,
"root:" + new_hash.replace('{CRYPT}', '')))
except IOError as e:
except IOError:
logger.warning(m18n.n('root_password_desynchronized'))
return
logger.info(m18n.n("root_password_replaced_by_admin_password"))
logger.success(m18n.n('admin_password_changed'))
@ -174,7 +174,7 @@ def tools_maindomain(operation_logger, auth, new_domain=None):
# Check domain exists
if new_domain not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
raise YunohostError('domain_unknown')
operation_logger.related_to.append(('domain', new_domain))
operation_logger.start()
@ -197,7 +197,7 @@ def tools_maindomain(operation_logger, auth, new_domain=None):
_set_maindomain(new_domain)
except Exception as e:
logger.warning("%s" % e, exc_info=1)
raise MoulinetteError(errno.EPERM, m18n.n('maindomain_change_failed'))
raise YunohostError('maindomain_change_failed')
_set_hostname(new_domain)
@ -206,7 +206,7 @@ def tools_maindomain(operation_logger, auth, new_domain=None):
# Regen configurations
try:
with open('/etc/yunohost/installed', 'r') as f:
with open('/etc/yunohost/installed', 'r'):
service_regen_conf()
except IOError:
pass
@ -246,7 +246,7 @@ def _set_hostname(hostname, pretty_hostname=None):
if p.returncode != 0:
logger.warning(command)
logger.warning(out)
raise MoulinetteError(errno.EIO, m18n.n('domain_hostname_failed'))
raise YunohostError('domain_hostname_failed')
else:
logger.debug(out)
@ -264,7 +264,7 @@ def _is_inside_container():
stderr=subprocess.STDOUT)
out, _ = p.communicate()
container = ['lxc','lxd','docker']
container = ['lxc', 'lxd', 'docker']
return out.split()[0] in container
@ -287,8 +287,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
# Do some checks at first
if os.path.isfile('/etc/yunohost/installed'):
raise MoulinetteError(errno.EPERM,
m18n.n('yunohost_already_installed'))
raise YunohostError('yunohost_already_installed')
# Check password
if not force_password:
@ -317,15 +316,12 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
dyndns = True
# If not, abort the postinstall
else:
raise MoulinetteError(errno.EEXIST,
m18n.n('dyndns_unavailable',
domain=domain))
raise YunohostError('dyndns_unavailable', domain=domain)
else:
dyndns = False
else:
dyndns = False
operation_logger.start()
logger.info(m18n.n('yunohost_installing'))
@ -362,8 +358,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
with open('/etc/ssowat/conf.json.persistent') as json_conf:
ssowat_conf = json.loads(str(json_conf.read()))
except ValueError as e:
raise MoulinetteError(errno.EINVAL,
m18n.n('ssowat_persistent_conf_read_error', error=str(e)))
raise YunohostError('ssowat_persistent_conf_read_error', error=str(e))
except IOError:
ssowat_conf = {}
@ -376,8 +371,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
json.dump(ssowat_conf, f, sort_keys=True, indent=4)
except IOError as e:
raise MoulinetteError(errno.EPERM,
m18n.n('ssowat_persistent_conf_write_error', error=str(e)))
raise YunohostError('ssowat_persistent_conf_write_error', error=str(e))
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
@ -404,8 +398,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
if p.returncode != 0:
logger.warning(out)
raise MoulinetteError(errno.EPERM,
m18n.n('yunohost_ca_creation_failed'))
raise YunohostError('yunohost_ca_creation_failed')
else:
logger.debug(out)
@ -432,7 +425,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
_install_appslist_fetch_cron()
# Init migrations (skip them, no need to run them on a fresh system)
tools_migrations_migrate(skip=True, auto=True)
_skip_all_migrations()
os.system('touch /etc/yunohost/installed')
@ -441,6 +434,24 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
service_start("yunohost-firewall")
service_regen_conf(force=True)
# Restore original ssh conf, as chosen by the
# admin during the initial install
#
# c.f. the install script and in particular
# https://github.com/YunoHost/install_script/pull/50
# The user can now choose during the install to keep
# the initial, existing sshd configuration
# instead of YunoHost's recommended conf
#
original_sshd_conf = '/etc/ssh/sshd_config.before_yunohost'
if os.path.exists(original_sshd_conf):
os.rename(original_sshd_conf, '/etc/ssh/sshd_config')
else:
# We need to explicitly ask the regen conf to regen ssh
# (by default, i.e. first argument = None, it won't because it's too touchy)
service_regen_conf(names=["ssh"], force=True)
logger.success(m18n.n('yunohost_configured'))
logger.warning(m18n.n('recommend_to_add_first_user'))
@ -463,7 +474,7 @@ def tools_update(ignore_apps=False, ignore_packages=False):
# Update APT cache
logger.debug(m18n.n('updating_apt_cache'))
if not cache.update():
raise MoulinetteError(errno.EPERM, m18n.n('update_cache_failed'))
raise YunohostError('update_cache_failed')
cache.open(None)
cache.upgrade(True)
@ -482,7 +493,7 @@ def tools_update(ignore_apps=False, ignore_packages=False):
if not ignore_apps:
try:
app_fetchlist()
except MoulinetteError:
except YunohostError:
# FIXME : silent exception !?
pass
@ -526,7 +537,7 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal
# If API call
if is_api:
critical_packages = ("moulinette", "yunohost",
"yunohost-admin", "ssowat", "python")
"yunohost-admin", "ssowat", "python")
critical_upgrades = set()
for pkg in cache.get_changes():
@ -562,7 +573,6 @@ def tools_upgrade(operation_logger, auth, ignore_apps=False, ignore_packages=Fal
else:
logger.info(m18n.n('packages_no_upgrade'))
if not ignore_apps:
try:
app_upgrade(auth)
@ -613,7 +623,7 @@ def tools_diagnosis(auth, private=False):
diagnosis['system'] = OrderedDict()
try:
disks = monitor_disk(units=['filesystem'], human_readable=True)
except (MoulinetteError, Fault) as e:
except (YunohostError, Fault) as e:
logger.warning(m18n.n('diagnosis_monitor_disk_error', error=format(e)), exc_info=1)
else:
diagnosis['system']['disks'] = {}
@ -629,7 +639,7 @@ def tools_diagnosis(auth, private=False):
try:
system = monitor_system(units=['cpu', 'memory'], human_readable=True)
except MoulinetteError as e:
except YunohostError as e:
logger.warning(m18n.n('diagnosis_monitor_system_error', error=format(e)), exc_info=1)
else:
diagnosis['system']['memory'] = {
@ -655,7 +665,7 @@ def tools_diagnosis(auth, private=False):
# YNH Applications
try:
applications = app_list()['apps']
except MoulinetteError as e:
except YunohostError as e:
diagnosis['applications'] = m18n.n('diagnosis_no_apps')
else:
diagnosis['applications'] = {}
@ -706,7 +716,7 @@ def _check_if_vulnerable_to_meltdown():
try:
call = subprocess.Popen("bash %s --batch json --variant 3" %
SCRIPT_PATH, shell=True,
stdout=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
output, _ = call.communicate()
@ -787,7 +797,7 @@ def tools_migrations_list(pending=False, done=False):
# Check for option conflict
if pending and done:
raise MoulinetteError(errno.EINVAL, m18n.n("migrations_list_conflict_pending_done"))
raise YunohostError("migrations_list_conflict_pending_done")
# Get all migrations
migrations = _get_migrations_list()
@ -802,12 +812,12 @@ def tools_migrations_list(pending=False, done=False):
migrations = [m for m in migrations if m.number > last_migration]
# Reduce to dictionnaries
migrations = [{ "id": migration.id,
"number": migration.number,
"name": migration.name,
"mode": migration.mode,
"description": migration.description,
"disclaimer": migration.disclaimer } for migration in migrations ]
migrations = [{"id": migration.id,
"number": migration.number,
"name": migration.name,
"mode": migration.mode,
"description": migration.description,
"disclaimer": migration.disclaimer} for migration in migrations]
return {"migrations": migrations}
@ -844,7 +854,7 @@ def tools_migrations_migrate(target=None, skip=False, auto=False, accept_disclai
# validate input, target must be "0" or a valid number
elif target != 0 and target not in all_migration_numbers:
raise MoulinetteError(errno.EINVAL, m18n.n('migrations_bad_value_for_target', ", ".join(map(str, all_migration_numbers))))
raise YunohostError('migrations_bad_value_for_target', ", ".join(map(str, all_migration_numbers)))
logger.debug(m18n.n('migrations_current_target', target))
@ -872,38 +882,41 @@ def tools_migrations_migrate(target=None, skip=False, auto=False, accept_disclai
else: # can't happen, this case is handle before
raise Exception()
# If we are migrating in "automatic mode" (i.e. from debian
# configure during an upgrade of the package) but we are asked to run
# migrations is to be ran manually by the user
manual_migrations = [m for m in migrations if m.mode == "manual"]
if not skip and auto and manual_migrations:
for m in manual_migrations:
logger.warn(m18n.n('migrations_to_be_ran_manually',
number=m.number,
name=m.name))
return
# If some migrations have disclaimers, require the --accept-disclaimer
# option
migrations_with_disclaimer = [m for m in migrations if m.disclaimer]
if not skip and not accept_disclaimer and migrations_with_disclaimer:
for m in migrations_with_disclaimer:
logger.warn(m18n.n('migrations_need_to_accept_disclaimer',
number=m.number,
name=m.name,
disclaimer=m.disclaimer))
return
# effectively run selected migrations
for migration in migrations:
if not skip:
# If we are migrating in "automatic mode" (i.e. from debian configure
# during an upgrade of the package) but we are asked to run migrations
# to be ran manually by the user, stop there and ask the user to
# run the migration manually.
if auto and migration.mode == "manual":
logger.warn(m18n.n('migrations_to_be_ran_manually',
number=migration.number,
name=migration.name))
break
# If some migrations have disclaimers,
if migration.disclaimer:
# require the --accept-disclaimer option. Otherwise, stop everything
# here and display the disclaimer
if not accept_disclaimer:
logger.warn(m18n.n('migrations_need_to_accept_disclaimer',
number=migration.number,
name=migration.name,
disclaimer=migration.disclaimer))
break
# --accept-disclaimer will only work for the first migration
else:
accept_disclaimer = False
# Start register change on system
operation_logger= OperationLogger('tools_migrations_migrate_' + mode)
operation_logger = OperationLogger('tools_migrations_migrate_' + mode)
operation_logger.start()
if not skip:
logger.warn(m18n.n('migrations_show_currently_running_migration',
logger.info(m18n.n('migrations_show_currently_running_migration',
number=migration.number, name=migration.name))
try:
@ -918,12 +931,15 @@ def tools_migrations_migrate(target=None, skip=False, auto=False, accept_disclai
# migration failed, let's stop here but still update state because
# we managed to run the previous ones
msg = m18n.n('migrations_migration_has_failed',
exception=e,
number=migration.number,
name=migration.name)
exception=e,
number=migration.number,
name=migration.name)
logger.error(msg, exc_info=1)
operation_logger.error(msg)
break
else:
logger.success(m18n.n('migrations_success',
number=migration.number, name=migration.name))
else: # if skip
logger.warn(m18n.n('migrations_skip_migration',
@ -938,6 +954,10 @@ def tools_migrations_migrate(target=None, skip=False, auto=False, accept_disclai
operation_logger.success()
# Skip migrations one at a time
if skip:
break
# special case where we want to go back from the start
if target == 0:
state["last_run_migration"] = None
@ -987,7 +1007,7 @@ def _get_migrations_list():
migrations = []
try:
import data_migrations
from . import data_migrations
except ImportError:
# not data migrations present, return empty list
return migrations
@ -1010,7 +1030,7 @@ def _get_migration_by_name(migration_name):
"""
try:
import data_migrations
from . import data_migrations
except ImportError:
raise AssertionError("Unable to find migration with name %s" % migration_name)
@ -1029,7 +1049,7 @@ def _load_migration(migration_file):
number, name = migration_id.split("_", 1)
logger.debug(m18n.n('migrations_loading_migration',
number=number, name=name))
number=number, name=name))
try:
# this is python builtin method to import a module using a name, we
@ -1041,8 +1061,28 @@ def _load_migration(migration_file):
import traceback
traceback.print_exc()
raise MoulinetteError(errno.EINVAL, m18n.n('migrations_error_failed_to_load_migration',
number=number, name=name))
raise YunohostError('migrations_error_failed_to_load_migration',
number=number, name=name)
def _skip_all_migrations():
"""
Skip all pending migrations.
This is meant to be used during postinstall to
initialize the migration system.
"""
state = tools_migrations_state()
# load all migrations
migrations = _get_migrations_list()
migrations = sorted(migrations, key=lambda x: x.number)
last_migration = migrations[-1]
state["last_run_migration"] = {
"number": last_migration.number,
"name": last_migration.name
}
write_to_json(MIGRATIONS_STATE_PATH, state)
class Migration(object):
@ -1074,4 +1114,3 @@ class Migration(object):
@property
def description(self):
return m18n.n("migration_description_%s" % self.id)

View file

@ -27,20 +27,20 @@ import os
import re
import pwd
import json
import errno
import crypt
import random
import string
import subprocess
from moulinette import m18n
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
from yunohost.service import service_status
from yunohost.log import is_unit_operation
logger = getActionLogger('yunohost.user')
def user_list(auth, fields=None):
"""
List users
@ -71,8 +71,7 @@ def user_list(auth, fields=None):
if attr in keys:
attrs.append(attr)
else:
raise MoulinetteError(errno.EINVAL,
m18n.n('field_invalid', attr))
raise YunohostError('field_invalid', attr)
else:
attrs = ['uid', 'cn', 'mail', 'mailuserquota', 'loginShell']
@ -100,7 +99,7 @@ def user_list(auth, fields=None):
@is_unit_operation([('username', 'user')])
def user_create(operation_logger, auth, username, firstname, lastname, mail, password,
mailbox_quota="0"):
mailbox_quota="0"):
"""
Create user
@ -130,7 +129,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
# Validate uniqueness of username in system users
all_existing_usernames = {x.pw_name for x in pwd.getpwall()}
if username in all_existing_usernames:
raise MoulinetteError(errno.EEXIST, m18n.n('system_username_exists'))
raise YunohostError('system_username_exists')
main_domain = _get_maindomain()
aliases = [
@ -141,13 +140,11 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
]
if mail in aliases:
raise MoulinetteError(errno.EEXIST,m18n.n('mail_unavailable'))
raise YunohostError('mail_unavailable')
# Check that the mail domain exists
if mail.split("@")[1] not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL,
m18n.n('mail_domain_unknown',
domain=mail.split("@")[1]))
raise YunohostError('mail_domain_unknown', domain=mail.split("@")[1])
operation_logger.start()
@ -188,8 +185,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
with open('/etc/ssowat/conf.json.persistent') as json_conf:
ssowat_conf = json.loads(str(json_conf.read()))
except ValueError as e:
raise MoulinetteError(errno.EINVAL,
m18n.n('ssowat_persistent_conf_read_error', error=e.strerror))
raise YunohostError('ssowat_persistent_conf_read_error', error=e.strerror)
except IOError:
ssowat_conf = {}
@ -199,8 +195,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
json.dump(ssowat_conf, f, sort_keys=True, indent=4)
except IOError as e:
raise MoulinetteError(errno.EPERM,
m18n.n('ssowat_persistent_conf_write_error', error=e.strerror))
raise YunohostError('ssowat_persistent_conf_write_error', error=e.strerror)
if auth.add('uid=%s,ou=users' % username, attr_dict):
# Invalidate passwd to take user creation into account
@ -226,7 +221,7 @@ def user_create(operation_logger, auth, username, firstname, lastname, mail, pas
return {'fullname': fullname, 'username': username, 'mail': mail}
raise MoulinetteError(169, m18n.n('user_creation_failed'))
raise YunohostError('user_creation_failed')
@is_unit_operation([('username', 'user')])
@ -258,7 +253,7 @@ def user_delete(operation_logger, auth, username, purge=False):
subprocess.call(['rm', '-rf', '/home/{0}'.format(username)])
subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)])
else:
raise MoulinetteError(169, m18n.n('user_deletion_failed'))
raise YunohostError('user_deletion_failed')
app_ssowatconf(auth)
@ -269,8 +264,8 @@ def user_delete(operation_logger, auth, username, purge=False):
@is_unit_operation([('username', 'user')], exclude=['auth', 'change_password'])
def user_update(operation_logger, auth, username, firstname=None, lastname=None, mail=None,
change_password=None, add_mailforward=None, remove_mailforward=None,
add_mailalias=None, remove_mailalias=None, mailbox_quota=None):
change_password=None, add_mailforward=None, remove_mailforward=None,
add_mailalias=None, remove_mailalias=None, mailbox_quota=None):
"""
Update user informations
@ -286,7 +281,7 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
remove_mailalias -- Mail aliases to remove
"""
from yunohost.domain import domain_list
from yunohost.domain import domain_list, _get_maindomain
from yunohost.app import app_ssowatconf
from yunohost.utils.password import assert_password_is_strong_enough
@ -297,7 +292,7 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
# Populate user informations
result = auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch)
if not result:
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
raise YunohostError('user_unknown', user=username)
user = result[0]
# Get modifications from arguments
@ -328,11 +323,9 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
]
auth.validate_uniqueness({'mail': mail})
if mail[mail.find('@') + 1:] not in domains:
raise MoulinetteError(errno.EINVAL,
m18n.n('mail_domain_unknown',
domain=mail[mail.find('@') + 1:]))
raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:])
if mail in aliases:
raise MoulinetteError(errno.EEXIST,m18n.n('mail_unavailable'))
raise YunohostError('mail_unavailable')
del user['mail'][0]
new_attr_dict['mail'] = [mail] + user['mail']
@ -343,9 +336,7 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
for mail in add_mailalias:
auth.validate_uniqueness({'mail': mail})
if mail[mail.find('@') + 1:] not in domains:
raise MoulinetteError(errno.EINVAL,
m18n.n('mail_domain_unknown',
domain=mail[mail.find('@') + 1:]))
raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:])
user['mail'].append(mail)
new_attr_dict['mail'] = user['mail']
@ -356,8 +347,7 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
if len(user['mail']) > 1 and mail in user['mail'][1:]:
user['mail'].remove(mail)
else:
raise MoulinetteError(errno.EINVAL,
m18n.n('mail_alias_remove_failed', mail=mail))
raise YunohostError('mail_alias_remove_failed', mail=mail)
new_attr_dict['mail'] = user['mail']
if add_mailforward:
@ -376,8 +366,7 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
if len(user['maildrop']) > 1 and mail in user['maildrop'][1:]:
user['maildrop'].remove(mail)
else:
raise MoulinetteError(errno.EINVAL,
m18n.n('mail_forward_remove_failed', mail=mail))
raise YunohostError('mail_forward_remove_failed', mail=mail)
new_attr_dict['maildrop'] = user['maildrop']
if mailbox_quota is not None:
@ -390,7 +379,7 @@ def user_update(operation_logger, auth, username, firstname=None, lastname=None,
app_ssowatconf(auth)
return user_info(auth, username)
else:
raise MoulinetteError(169, m18n.n('user_update_failed'))
raise YunohostError('user_update_failed')
def user_info(auth, username):
@ -415,7 +404,7 @@ def user_info(auth, username):
if result:
user = result[0]
else:
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
raise YunohostError('user_unknown', user=username)
result_dict = {
'username': user['uid'][0],
@ -471,7 +460,7 @@ def user_info(auth, username):
if result:
return result_dict
else:
raise MoulinetteError(167, m18n.n('user_info_failed'))
raise YunohostError('user_info_failed')
#
# SSH subcategory
@ -479,18 +468,23 @@ def user_info(auth, username):
#
import yunohost.ssh
def user_ssh_allow(auth, username):
return yunohost.ssh.user_ssh_allow(auth, username)
def user_ssh_disallow(auth, username):
return yunohost.ssh.user_ssh_disallow(auth, username)
def user_ssh_list_keys(auth, username):
return yunohost.ssh.user_ssh_list_keys(auth, username)
def user_ssh_add_key(auth, username, key, comment):
return yunohost.ssh.user_ssh_add_key(auth, username, key, comment)
def user_ssh_remove_key(auth, username, key):
return yunohost.ssh.user_ssh_remove_key(auth, username, key)
@ -498,6 +492,7 @@ def user_ssh_remove_key(auth, username, key):
# End SSH subcategory
#
def _convertSize(num, suffix=''):
for unit in ['K', 'M', 'G', 'T', 'P', 'E', 'Z']:
if abs(num) < 1024.0:

View file

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2018 YUNOHOST.ORG
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program; if not, see http://www.gnu.org/licenses
"""
from moulinette.core import MoulinetteError
from moulinette import m18n
class YunohostError(MoulinetteError):
"""
Yunohost base exception
The (only?) main difference with MoulinetteError being that keys
are translated via m18n.n (namespace) instead of m18n.g (global?)
"""
def __init__(self, key, __raw_msg__=False, *args, **kwargs):
if __raw_msg__:
msg = key
else:
msg = m18n.n(key, *args, **kwargs)
super(YunohostError, self).__init__(msg, __raw_msg__=True)

View file

@ -20,10 +20,12 @@
"""
import os
def free_space_in_directory(dirpath):
stat = os.statvfs(dirpath)
return stat.f_frsize * stat.f_bavail
def space_used_by_directory(dirpath):
stat = os.statvfs(dirpath)
return stat.f_frsize * stat.f_blocks

View file

@ -71,7 +71,7 @@ def get_gateway():
return addr.popitem()[1] if len(addr) == 1 else None
###############################################################################
#
def _extract_inet(string, skip_netmask=False, skip_loopback=True):

View file

@ -33,6 +33,7 @@ logger = logging.getLogger('yunohost.utils.packages')
# Exceptions -----------------------------------------------------------------
class PackageException(Exception):
"""Base exception related to a package
Represent an exception related to the package named `pkgname`. If no
@ -50,16 +51,19 @@ class PackageException(Exception):
class UnknownPackage(PackageException):
"""The package is not found in the cache."""
message_key = 'package_unknown'
class UninstalledPackage(PackageException):
"""The package is not installed."""
message_key = 'package_not_installed'
class InvalidSpecifier(ValueError):
"""An invalid specifier was found."""
@ -68,6 +72,7 @@ class InvalidSpecifier(ValueError):
# See: https://github.com/pypa/packaging
class Specifier(object):
"""Unique package version specifier
Restrict a package version according to the `spec`. It must be a string
@ -257,6 +262,7 @@ class Specifier(object):
class SpecifierSet(object):
"""A set of package version specifiers
Combine several Specifier separated by a comma. It allows to restrict

View file

@ -38,9 +38,11 @@ STRENGTH_LEVELS = [
(12, 1, 1, 1, 1),
]
def assert_password_is_strong_enough(profile, password):
PasswordValidator(profile).validate(password)
class PasswordValidator(object):
def __init__(self, profile):
@ -81,17 +83,15 @@ class PasswordValidator(object):
# 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 yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
logger = logging.getLogger('yunohost.utils.password')
status, msg = self.validation_summary(password)
if status == "error":
raise MoulinetteError(1, m18n.n(msg))
raise YunohostError(msg)
def validation_summary(self, password):
"""
@ -159,7 +159,7 @@ class PasswordValidator(object):
# 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)]:
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
@ -188,7 +188,7 @@ if __name__ == '__main__':
if len(sys.argv) < 2:
import getpass
pwd = getpass.getpass("")
#print("usage: password.py PASSWORD")
# print("usage: password.py PASSWORD")
else:
pwd = sys.argv[1]
status, msg = PasswordValidator('user').validation_summary(pwd)

View file

@ -2,9 +2,9 @@
import requests
import json
import errno
from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError
def yunopaste(data):
@ -13,17 +13,14 @@ def yunopaste(data):
try:
r = requests.post("%s/documents" % paste_server, data=data, timeout=30)
except Exception as e:
raise MoulinetteError(errno.EIO,
"Something wrong happened while trying to paste data on paste.yunohost.org : %s" % str(e))
raise YunohostError("Something wrong happened while trying to paste data on paste.yunohost.org : %s" % str(e), raw_msg=True)
if r.status_code != 200:
raise MoulinetteError(errno.EIO,
"Something wrong happened while trying to paste data on paste.yunohost.org : %s, %s" % (r.status_code, r.text))
raise YunohostError("Something wrong happened while trying to paste data on paste.yunohost.org : %s, %s" % (r.status_code, r.text), raw_msg=True)
try:
url = json.loads(r.text)["key"]
except:
raise MoulinetteError(errno.EIO,
"Uhoh, couldn't parse the answer from paste.yunohost.org : %s" % r.text)
raise YunohostError("Uhoh, couldn't parse the answer from paste.yunohost.org : %s" % r.text, raw_msg=True)
return "%s/raw/%s" % (paste_server, url)