mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge branch 'unstable' into NO_BACKUP_UPGRADE
This commit is contained in:
commit
b3c600ac1d
23 changed files with 519 additions and 111 deletions
|
@ -203,6 +203,30 @@ user:
|
|||
extra:
|
||||
pattern: *pattern_mailbox_quota
|
||||
|
||||
### ssh_user_enable_ssh()
|
||||
allow-ssh:
|
||||
action_help: Allow the user to uses ssh
|
||||
api: POST /ssh/user/enable-ssh
|
||||
configuration:
|
||||
authenticate: all
|
||||
arguments:
|
||||
username:
|
||||
help: Username of the user
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
|
||||
### ssh_user_disable_ssh()
|
||||
disallow-ssh:
|
||||
action_help: Disallow the user to uses ssh
|
||||
api: POST /ssh/user/disable-ssh
|
||||
configuration:
|
||||
authenticate: all
|
||||
arguments:
|
||||
username:
|
||||
help: Username of the user
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
|
||||
### user_info()
|
||||
info:
|
||||
action_help: Get user information
|
||||
|
@ -1325,6 +1349,74 @@ dyndns:
|
|||
api: DELETE /dyndns/cron
|
||||
|
||||
|
||||
#############################
|
||||
# SSH #
|
||||
#############################
|
||||
ssh:
|
||||
category_help: Manage ssh keys and access
|
||||
actions: {}
|
||||
subcategories:
|
||||
authorized-keys:
|
||||
subcategory_help: Manage user's authorized ssh keys
|
||||
|
||||
actions:
|
||||
### ssh_authorized_keys_list()
|
||||
list:
|
||||
action_help: Show user's authorized ssh keys
|
||||
api: GET /ssh/authorized-keys
|
||||
configuration:
|
||||
authenticate: all
|
||||
arguments:
|
||||
username:
|
||||
help: Username of the user
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
|
||||
### ssh_authorized_keys_add()
|
||||
add:
|
||||
action_help: Add a new authorized ssh key for this user
|
||||
api: POST /ssh/authorized-keys
|
||||
configuration:
|
||||
authenticate: all
|
||||
arguments:
|
||||
username:
|
||||
help: Username of the user
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
-u:
|
||||
full: --public
|
||||
help: Public key
|
||||
extra:
|
||||
required: True
|
||||
-i:
|
||||
full: --private
|
||||
help: Private key
|
||||
extra:
|
||||
required: True
|
||||
-n:
|
||||
full: --name
|
||||
help: Key name
|
||||
extra:
|
||||
required: True
|
||||
|
||||
### ssh_authorized_keys_remove()
|
||||
remove:
|
||||
action_help: Remove an authorized ssh key for this user
|
||||
api: DELETE /ssh/authorized-keys
|
||||
configuration:
|
||||
authenticate: all
|
||||
arguments:
|
||||
username:
|
||||
help: Username of the user
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
-k:
|
||||
full: --key
|
||||
help: Key as a string
|
||||
extra:
|
||||
required: True
|
||||
|
||||
|
||||
#############################
|
||||
# Tools #
|
||||
#############################
|
||||
|
|
|
@ -64,9 +64,13 @@ ynh_remove_logrotate () {
|
|||
|
||||
# Create a dedicated systemd config
|
||||
#
|
||||
# This will use a template in ../conf/systemd.service
|
||||
# and will replace the following keywords with
|
||||
# global variables that should be defined before calling
|
||||
# usage: ynh_add_systemd_config [Service name] [Template name]
|
||||
# | arg: Service name (optionnal, $app by default)
|
||||
# | arg: Name of template file (optionnal, this is 'systemd' by default, meaning ./conf/systemd.service will be used as template)
|
||||
#
|
||||
# This will use the template ../conf/<templatename>.service
|
||||
# to generate a systemd config, by replacing the following keywords
|
||||
# with global variables that should be defined before calling
|
||||
# this helper :
|
||||
#
|
||||
# __APP__ by $app
|
||||
|
@ -74,9 +78,11 @@ ynh_remove_logrotate () {
|
|||
#
|
||||
# usage: ynh_add_systemd_config
|
||||
ynh_add_systemd_config () {
|
||||
finalsystemdconf="/etc/systemd/system/$app.service"
|
||||
local service_name="${1:-$app}"
|
||||
|
||||
finalsystemdconf="/etc/systemd/system/$service_name.service"
|
||||
ynh_backup_if_checksum_is_different "$finalsystemdconf"
|
||||
sudo cp ../conf/systemd.service "$finalsystemdconf"
|
||||
sudo cp ../conf/${2:-systemd.service} "$finalsystemdconf"
|
||||
|
||||
# To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable.
|
||||
# Substitute in a nginx config file only if the variable is not empty
|
||||
|
@ -89,19 +95,25 @@ ynh_add_systemd_config () {
|
|||
ynh_store_file_checksum "$finalsystemdconf"
|
||||
|
||||
sudo chown root: "$finalsystemdconf"
|
||||
sudo systemctl enable $app
|
||||
sudo systemctl enable $service_name
|
||||
sudo systemctl daemon-reload
|
||||
}
|
||||
|
||||
# Remove the dedicated systemd config
|
||||
#
|
||||
# usage: ynh_remove_systemd_config [Service name]
|
||||
# | arg: Service name (optionnal, $app by default)
|
||||
#
|
||||
# usage: ynh_remove_systemd_config
|
||||
ynh_remove_systemd_config () {
|
||||
local finalsystemdconf="/etc/systemd/system/$app.service"
|
||||
local service_name="${1:-$app}"
|
||||
|
||||
local finalsystemdconf="/etc/systemd/system/$service_name.service"
|
||||
if [ -e "$finalsystemdconf" ]; then
|
||||
sudo systemctl stop $app
|
||||
sudo systemctl disable $app
|
||||
sudo systemctl stop $service_name
|
||||
sudo systemctl disable $service_name
|
||||
ynh_secure_remove "$finalsystemdconf"
|
||||
sudo systemctl daemon-reload
|
||||
fi
|
||||
}
|
||||
|
||||
|
@ -157,7 +169,17 @@ ynh_remove_nginx_config () {
|
|||
#
|
||||
# usage: ynh_add_fpm_config
|
||||
ynh_add_fpm_config () {
|
||||
finalphpconf="/etc/php5/fpm/pool.d/$app.conf"
|
||||
# Configure PHP-FPM 7.0 by default
|
||||
local fpm_config_dir="/etc/php/7.0/fpm"
|
||||
local fpm_service="php7.0-fpm"
|
||||
# Configure PHP-FPM 5 on Debian Jessie
|
||||
if [ "$(ynh_get_debian_release)" == "jessie" ]; then
|
||||
fpm_config_dir="/etc/php5/fpm"
|
||||
fpm_service="php5-fpm"
|
||||
fi
|
||||
ynh_app_setting_set $app fpm_config_dir "$fpm_config_dir"
|
||||
ynh_app_setting_set $app fpm_service "$fpm_service"
|
||||
finalphpconf="$fpm_config_dir/pool.d/$app.conf"
|
||||
ynh_backup_if_checksum_is_different "$finalphpconf"
|
||||
sudo cp ../conf/php-fpm.conf "$finalphpconf"
|
||||
ynh_replace_string "__NAMETOCHANGE__" "$app" "$finalphpconf"
|
||||
|
@ -168,21 +190,27 @@ ynh_add_fpm_config () {
|
|||
|
||||
if [ -e "../conf/php-fpm.ini" ]
|
||||
then
|
||||
finalphpini="/etc/php5/fpm/conf.d/20-$app.ini"
|
||||
finalphpini="$fpm_config_dir/conf.d/20-$app.ini"
|
||||
ynh_backup_if_checksum_is_different "$finalphpini"
|
||||
sudo cp ../conf/php-fpm.ini "$finalphpini"
|
||||
sudo chown root: "$finalphpini"
|
||||
ynh_store_file_checksum "$finalphpini"
|
||||
fi
|
||||
|
||||
sudo systemctl reload php5-fpm
|
||||
sudo systemctl reload $fpm_service
|
||||
}
|
||||
|
||||
# Remove the dedicated php-fpm config
|
||||
#
|
||||
# usage: ynh_remove_fpm_config
|
||||
ynh_remove_fpm_config () {
|
||||
ynh_secure_remove "/etc/php5/fpm/pool.d/$app.conf"
|
||||
ynh_secure_remove "/etc/php5/fpm/conf.d/20-$app.ini" 2>&1
|
||||
sudo systemctl reload php5-fpm
|
||||
local fpm_config_dir=$(ynh_app_setting_get $app fpm_config_dir)
|
||||
local fpm_service=$(ynh_app_setting_get $app fpm_service)
|
||||
# Assume php version 5 if not set
|
||||
if [ -z "$fpm_config_dir" ]; then
|
||||
fpm_config_dir="/etc/php5/fpm"
|
||||
fpm_service="php5-fpm"
|
||||
fi
|
||||
ynh_secure_remove "$fpm_config_dir/pool.d/$app.conf"
|
||||
ynh_secure_remove "$fpm_config_dir/conf.d/20-$app.ini" 2>&1
|
||||
sudo systemctl reload $fpm_service
|
||||
}
|
||||
|
|
|
@ -41,3 +41,10 @@ ynh_abort_if_errors () {
|
|||
set -eu # Exit if a command fail, and if a variable is used unset.
|
||||
trap ynh_exit_properly EXIT # Capturing exit signals on shell script
|
||||
}
|
||||
|
||||
# Return the Debian release codename (i.e. jessie, stretch, etc.)
|
||||
#
|
||||
# usage: ynh_get_debian_release
|
||||
ynh_get_debian_release () {
|
||||
echo $(lsb_release --codename --short)
|
||||
}
|
|
@ -37,22 +37,23 @@ ynh_get_plain_key() {
|
|||
ynh_restore_upgradebackup () {
|
||||
echo "Upgrade failed." >&2
|
||||
local app_bck=${app//_/-} # Replace all '_' by '-'
|
||||
NO_BACKUP_UPGRADE=${NO_BACKUP_UPGRADE:-0}
|
||||
|
||||
if [ "$NO_BACKUP_UPGRADE" -eq 0 ]
|
||||
then
|
||||
NO_BACKUP_UPGRADE=${NO_BACKUP_UPGRADE:-0}
|
||||
|
||||
if [ "$NO_BACKUP_UPGRADE" -eq 0 ]
|
||||
then
|
||||
# Check if an existing backup can be found before removing and restoring the application.
|
||||
if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$backup_number
|
||||
then
|
||||
# Remove the application then restore it
|
||||
sudo yunohost app remove $app
|
||||
# Restore the backup
|
||||
sudo yunohost backup restore --ignore-system $app_bck-pre-upgrade$backup_number --apps $app --force
|
||||
sudo yunohost backup restore --ignore-system $app_bck-pre-upgrade$backup_number --apps $app --force --verbose
|
||||
ynh_die "The app was restored to the way it was before the failed upgrade."
|
||||
fi
|
||||
else
|
||||
echo "\$NO_BACKUP_UPGRADE is set, that means there's no backup to restore. You have to fix this upgrade by yourself !" >&2
|
||||
fi
|
||||
else
|
||||
echo "\$NO_BACKUP_UPGRADE is set, that means there's no backup to restore. You have to fix this upgrade by yourself !" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
# Make a backup in case of failed upgrade
|
||||
|
@ -86,7 +87,7 @@ ynh_backup_before_upgrade () {
|
|||
fi
|
||||
|
||||
# Create backup
|
||||
sudo yunohost backup create --ignore-system --apps $app --name $app_bck-pre-upgrade$backup_number
|
||||
sudo yunohost backup create --ignore-system --apps $app --name $app_bck-pre-upgrade$backup_number --verbose
|
||||
if [ "$?" -eq 0 ]
|
||||
then
|
||||
# If the backup succeeded, remove the previous backup
|
||||
|
|
|
@ -53,6 +53,9 @@ do_pre_regen() {
|
|||
else
|
||||
sudo cp services.yml /etc/yunohost/services.yml
|
||||
fi
|
||||
|
||||
mkdir -p "$pending_dir"/etc/etckeeper/
|
||||
cp etckeeper.conf "$pending_dir"/etc/etckeeper/
|
||||
}
|
||||
|
||||
_update_services() {
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
server_tokens off;
|
||||
gzip_types text/css text/javascript application/javascript;
|
||||
|
|
|
@ -37,7 +37,17 @@ server {
|
|||
# > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048
|
||||
#ssl_dhparam /etc/ssl/private/dh2048.pem;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000;";
|
||||
# Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners
|
||||
# https://wiki.mozilla.org/Security/Guidelines/Web_Security
|
||||
# https://observatory.mozilla.org/
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
|
||||
add_header 'Referrer-Policy' 'same-origin';
|
||||
add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Download-Options noopen;
|
||||
add_header X-Permitted-Cross-Domain-Policies none;
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
|
||||
location / {
|
||||
return 302 https://$http_host/yunohost/admin;
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
location /yunohost/admin {
|
||||
# Avoid the nginx path/alias traversal weakness ( #1037 )
|
||||
rewrite ^/yunohost/admin$ /yunohost/admin/ permanent;
|
||||
|
||||
location /yunohost/admin/ {
|
||||
alias /usr/share/yunohost/admin/;
|
||||
default_type text/html;
|
||||
index index.html;
|
||||
|
|
|
@ -42,7 +42,16 @@ server {
|
|||
# > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048
|
||||
#ssl_dhparam /etc/ssl/private/dh2048.pem;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=31536000;";
|
||||
# Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners
|
||||
# https://wiki.mozilla.org/Security/Guidelines/Web_Security
|
||||
# https://observatory.mozilla.org/
|
||||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
|
||||
add_header Content-Security-Policy "upgrade-insecure-requests; object-src 'none'; script-src https: 'unsafe-eval'";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Download-Options noopen;
|
||||
add_header X-Permitted-Cross-Domain-Policies none;
|
||||
add_header X-Frame-Options "SAMEORIGIN";
|
||||
|
||||
access_by_lua_file /usr/share/ssowat/access.lua;
|
||||
|
||||
|
|
43
data/templates/yunohost/etckeeper.conf
Normal file
43
data/templates/yunohost/etckeeper.conf
Normal file
|
@ -0,0 +1,43 @@
|
|||
# The VCS to use.
|
||||
#VCS="hg"
|
||||
VCS="git"
|
||||
#VCS="bzr"
|
||||
#VCS="darcs"
|
||||
|
||||
# Options passed to git commit when run by etckeeper.
|
||||
GIT_COMMIT_OPTIONS="--quiet"
|
||||
|
||||
# Options passed to hg commit when run by etckeeper.
|
||||
HG_COMMIT_OPTIONS=""
|
||||
|
||||
# Options passed to bzr commit when run by etckeeper.
|
||||
BZR_COMMIT_OPTIONS=""
|
||||
|
||||
# Options passed to darcs record when run by etckeeper.
|
||||
DARCS_COMMIT_OPTIONS="-a"
|
||||
|
||||
# Uncomment to avoid etckeeper committing existing changes
|
||||
# to /etc automatically once per day.
|
||||
#AVOID_DAILY_AUTOCOMMITS=1
|
||||
|
||||
# Uncomment the following to avoid special file warning
|
||||
# (the option is enabled automatically by cronjob regardless).
|
||||
#AVOID_SPECIAL_FILE_WARNING=1
|
||||
|
||||
# Uncomment to avoid etckeeper committing existing changes to
|
||||
# /etc before installation. It will cancel the installation,
|
||||
# so you can commit the changes by hand.
|
||||
#AVOID_COMMIT_BEFORE_INSTALL=1
|
||||
|
||||
# The high-level package manager that's being used.
|
||||
# (apt, pacman-g2, yum, zypper etc)
|
||||
HIGHLEVEL_PACKAGE_MANAGER=apt
|
||||
|
||||
# The low-level package manager that's being used.
|
||||
# (dpkg, rpm, pacman, pacman-g2, etc)
|
||||
LOWLEVEL_PACKAGE_MANAGER=dpkg
|
||||
|
||||
# To push each commit to a remote, put the name of the remote here.
|
||||
# (eg, "origin" for git). Space-separated lists of multiple remotes
|
||||
# also work (eg, "origin gitlab github" for git).
|
||||
PUSH_REMOTE=""
|
2
debian/control
vendored
2
debian/control
vendored
|
@ -18,7 +18,7 @@ Depends: ${python:Depends}, ${misc:Depends}
|
|||
, ca-certificates, netcat-openbsd, iproute
|
||||
, mariadb-server | mysql-server, php5-mysql | php5-mysqlnd
|
||||
, slapd, ldap-utils, sudo-ldap, libnss-ldapd, nscd
|
||||
, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail
|
||||
, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail, mailutils
|
||||
, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved
|
||||
, dovecot-antispam, fail2ban
|
||||
, nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl
|
||||
|
|
2
locales/ar.json
Normal file
2
locales/ar.json
Normal file
|
@ -0,0 +1,2 @@
|
|||
{
|
||||
}
|
|
@ -14,6 +14,7 @@
|
|||
"app_change_url_identical_domains": "The old and new domain/url_path are identical ('{domain:s}{path:s}'), nothing to do.",
|
||||
"app_change_url_no_script": "This application '{app_name:s}' doesn't support url modification yet. Maybe you should upgrade the application.",
|
||||
"app_change_url_success": "Successfully changed {app:s} url to {domain:s}{path:s}",
|
||||
"app_checkurl_is_deprecated": "Packagers /!\\ 'app checkurl' is deprecated ! Please use 'app register-url' instead !",
|
||||
"app_extraction_failed": "Unable to extract installation files",
|
||||
"app_id_invalid": "Invalid app id",
|
||||
"app_incompatible": "The app {app} is incompatible with your YunoHost version",
|
||||
|
|
|
@ -432,7 +432,7 @@ def app_change_url(auth, app, domain, path):
|
|||
path -- New path at which the application will be move
|
||||
|
||||
"""
|
||||
from yunohost.hook import hook_exec
|
||||
from yunohost.hook import hook_exec, hook_callback
|
||||
|
||||
installed = _is_installed(app)
|
||||
if not installed:
|
||||
|
@ -485,6 +485,12 @@ def app_change_url(auth, app, domain, path):
|
|||
shutil.copytree(os.path.join(APPS_SETTING_PATH, app, "scripts"),
|
||||
os.path.join(APP_TMP_FOLDER, "scripts"))
|
||||
|
||||
if os.path.exists(os.path.join(APP_TMP_FOLDER, "conf")):
|
||||
shutil.rmtree(os.path.join(APP_TMP_FOLDER, "conf"))
|
||||
|
||||
shutil.copytree(os.path.join(APPS_SETTING_PATH, app, "conf"),
|
||||
os.path.join(APP_TMP_FOLDER, "conf"))
|
||||
|
||||
# Execute App change_url script
|
||||
os.system('chown -R admin: %s' % INSTALL_TMP)
|
||||
os.system('chmod +x %s' % os.path.join(os.path.join(APP_TMP_FOLDER, "scripts")))
|
||||
|
@ -521,6 +527,8 @@ def app_change_url(auth, app, domain, path):
|
|||
logger.success(m18n.n("app_change_url_success",
|
||||
app=app, domain=domain, path=path))
|
||||
|
||||
hook_callback('post_app_change_url', args=args_list, env=env_dict)
|
||||
|
||||
|
||||
def app_upgrade(auth, app=[], url=None, file=None):
|
||||
"""
|
||||
|
@ -532,7 +540,8 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
|||
url -- Git url to fetch for upgrade
|
||||
|
||||
"""
|
||||
from yunohost.hook import hook_add, hook_remove, hook_exec
|
||||
from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback
|
||||
|
||||
|
||||
# Retrieve interface
|
||||
is_api = msettings.get('interface') == 'api'
|
||||
|
@ -635,6 +644,9 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
|||
upgraded_apps.append(app_instance_name)
|
||||
logger.success(m18n.n('app_upgraded', app=app_instance_name))
|
||||
|
||||
hook_callback('post_app_upgrade', args=args_list, env=env_dict)
|
||||
|
||||
|
||||
if not upgraded_apps:
|
||||
raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade'))
|
||||
|
||||
|
@ -658,7 +670,7 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
|
|||
no_remove_on_failure -- Debug option to avoid removing the app on a failed installation
|
||||
|
||||
"""
|
||||
from yunohost.hook import hook_add, hook_remove, hook_exec
|
||||
from yunohost.hook import hook_add, hook_remove, hook_exec, hook_callback
|
||||
|
||||
# Fetch or extract sources
|
||||
try:
|
||||
|
@ -800,6 +812,8 @@ def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
|
|||
|
||||
logger.success(m18n.n('installation_complete'))
|
||||
|
||||
hook_callback('post_app_install', args=args_list, env=env_dict)
|
||||
|
||||
|
||||
def app_remove(auth, app):
|
||||
"""
|
||||
|
@ -809,7 +823,7 @@ def app_remove(auth, app):
|
|||
app -- App(s) to delete
|
||||
|
||||
"""
|
||||
from yunohost.hook import hook_exec, hook_remove
|
||||
from yunohost.hook import hook_exec, hook_remove, hook_callback
|
||||
|
||||
if not _is_installed(app):
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
|
@ -838,6 +852,8 @@ def app_remove(auth, app):
|
|||
if hook_exec('/tmp/yunohost_remove/scripts/remove', args=args_list, env=env_dict, user="root") == 0:
|
||||
logger.success(m18n.n('app_removed', app=app))
|
||||
|
||||
hook_callback('post_app_remove', args=args_list, env=env_dict)
|
||||
|
||||
if os.path.exists(app_setting_path):
|
||||
shutil.rmtree(app_setting_path)
|
||||
shutil.rmtree('/tmp/yunohost_remove')
|
||||
|
@ -1149,6 +1165,9 @@ def app_checkurl(auth, url, app=None):
|
|||
app -- Write domain & path to app settings for further checks
|
||||
|
||||
"""
|
||||
|
||||
logger.warning(m18n.n("app_checkurl_is_deprecated"))
|
||||
|
||||
from yunohost.domain import domain_list
|
||||
|
||||
if "https://" == url[:8]:
|
||||
|
|
|
@ -1543,9 +1543,13 @@ class BackupMethod(object):
|
|||
# Can create a hard link only if files are on the same fs
|
||||
# (i.e. we can't if it's on a different fs)
|
||||
if os.stat(src).st_dev == os.stat(dest_dir).st_dev:
|
||||
os.link(src, dest)
|
||||
# Success, go to next file to organize
|
||||
continue
|
||||
# Don't hardlink /etc/cron.d files to avoid cron bug
|
||||
# 'NUMBER OF HARD LINKS > 1' see #1043
|
||||
cron_path = os.path.abspath('/etc/cron') + '.'
|
||||
if not os.path.abspath(src).startswith(cron_path):
|
||||
os.link(src, dest)
|
||||
# Success, go to next file to organize
|
||||
continue
|
||||
|
||||
# If mountbind or hardlink couldnt be created,
|
||||
# prepare a list of files that need to be copied
|
||||
|
|
|
@ -44,6 +44,7 @@ from moulinette.core import MoulinetteError
|
|||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
import yunohost.domain
|
||||
from yunohost.utils.network import get_public_ip
|
||||
|
||||
from moulinette import m18n
|
||||
from yunohost.app import app_ssowatconf
|
||||
|
@ -809,7 +810,7 @@ def _backup_current_cert(domain):
|
|||
|
||||
|
||||
def _check_domain_is_ready_for_ACME(domain):
|
||||
public_ip = yunohost.domain.get_public_ip()
|
||||
public_ip = get_public_ip()
|
||||
|
||||
# Check if IP from DNS matches public IP
|
||||
if not _dns_ip_match_public_ip(public_ip, domain):
|
||||
|
@ -856,14 +857,9 @@ def _regen_dnsmasq_if_needed():
|
|||
"""
|
||||
Update the dnsmasq conf if some IPs are not up to date...
|
||||
"""
|
||||
try:
|
||||
ipv4 = yunohost.domain.get_public_ip()
|
||||
except:
|
||||
ipv4 = None
|
||||
try:
|
||||
ipv6 = yunohost.domain.get_public_ip(6)
|
||||
except:
|
||||
ipv6 = None
|
||||
|
||||
ipv4 = get_public_ip()
|
||||
ipv6 = get_public_ip(6)
|
||||
|
||||
do_regen = False
|
||||
|
||||
|
|
|
@ -30,8 +30,6 @@ import yaml
|
|||
import errno
|
||||
import requests
|
||||
|
||||
from urllib import urlopen
|
||||
|
||||
from moulinette import m18n, msettings
|
||||
from moulinette.core import MoulinetteError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
@ -39,6 +37,7 @@ from moulinette.utils.log import getActionLogger
|
|||
import yunohost.certificate
|
||||
|
||||
from yunohost.service import service_regen_conf
|
||||
from yunohost.utils.network import get_public_ip
|
||||
|
||||
logger = getActionLogger('yunohost.domain')
|
||||
|
||||
|
@ -260,42 +259,6 @@ def domain_url_available(auth, domain, path):
|
|||
return available
|
||||
|
||||
|
||||
def get_public_ip(protocol=4):
|
||||
"""Retrieve the public IP address from ip.yunohost.org"""
|
||||
if protocol == 4:
|
||||
url = 'https://ip.yunohost.org'
|
||||
elif protocol == 6:
|
||||
url = 'https://ip6.yunohost.org'
|
||||
else:
|
||||
raise ValueError("invalid protocol version")
|
||||
|
||||
try:
|
||||
return urlopen(url).read().strip()
|
||||
except IOError:
|
||||
logger.debug('cannot retrieve public IPv%d' % protocol, exc_info=1)
|
||||
raise MoulinetteError(errno.ENETUNREACH,
|
||||
m18n.n('no_internet_connection'))
|
||||
|
||||
def get_public_ips():
|
||||
"""
|
||||
Retrieve the public IPv4 and v6 from ip. and ip6.yunohost.org
|
||||
|
||||
Returns a 2-tuple (ipv4, ipv6). ipv4 or ipv6 can be None if they were not
|
||||
found.
|
||||
"""
|
||||
|
||||
try:
|
||||
ipv4 = get_public_ip()
|
||||
except:
|
||||
ipv4 = None
|
||||
try:
|
||||
ipv6 = get_public_ip(6)
|
||||
except:
|
||||
ipv6 = None
|
||||
|
||||
return (ipv4, ipv6)
|
||||
|
||||
|
||||
def _get_maindomain():
|
||||
with open('/etc/yunohost/current_host', 'r') as f:
|
||||
maindomain = f.readline().rstrip()
|
||||
|
@ -356,15 +319,8 @@ def _build_dns_conf(domain, ttl=3600):
|
|||
}
|
||||
"""
|
||||
|
||||
try:
|
||||
ipv4 = get_public_ip()
|
||||
except:
|
||||
ipv4 = None
|
||||
|
||||
try:
|
||||
ipv6 = get_public_ip(6)
|
||||
except:
|
||||
ipv6 = None
|
||||
ipv4 = get_public_ip()
|
||||
ipv6 = get_public_ip(6)
|
||||
|
||||
basic = []
|
||||
|
||||
|
|
|
@ -39,7 +39,8 @@ from moulinette.utils.log import getActionLogger
|
|||
from moulinette.utils.filesystem import read_file, write_to_file, rm
|
||||
from moulinette.utils.network import download_json
|
||||
|
||||
from yunohost.domain import get_public_ips, _get_maindomain, _build_dns_conf
|
||||
from yunohost.domain import _get_maindomain, _build_dns_conf
|
||||
from yunohost.utils.network import get_public_ip
|
||||
|
||||
logger = getActionLogger('yunohost.dyndns')
|
||||
|
||||
|
@ -193,7 +194,8 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None,
|
|||
old_ipv6 = read_file(OLD_IPV6_FILE).rstrip()
|
||||
|
||||
# Get current IPv4 and IPv6
|
||||
(ipv4_, ipv6_) = get_public_ips()
|
||||
ipv4_ = get_public_ip()
|
||||
ipv6_ = get_public_ip(6)
|
||||
|
||||
if ipv4 is None:
|
||||
ipv4 = ipv4_
|
||||
|
|
|
@ -41,7 +41,8 @@ from moulinette import m18n
|
|||
from moulinette.core import MoulinetteError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
from yunohost.domain import get_public_ip, _get_maindomain
|
||||
from yunohost.utils.network import get_public_ip
|
||||
from yunohost.domain import _get_maindomain
|
||||
|
||||
logger = getActionLogger('yunohost.monitor')
|
||||
|
||||
|
@ -210,10 +211,7 @@ def monitor_network(units=None, human_readable=False):
|
|||
else:
|
||||
logger.debug('interface name %s was not found', iname)
|
||||
elif u == 'infos':
|
||||
try:
|
||||
p_ipv4 = get_public_ip()
|
||||
except:
|
||||
p_ipv4 = 'unknown'
|
||||
p_ipv4 = get_public_ip() or 'unknown'
|
||||
|
||||
l_ip = 'unknown'
|
||||
for name, addrs in devices.items():
|
||||
|
|
102
src/yunohost/ssh.py
Normal file
102
src/yunohost/ssh.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
# encoding: utf-8
|
||||
|
||||
import os
|
||||
|
||||
from moulinette.utils.filesystem import read_file, write_to_file, chown, chmod, mkdir
|
||||
|
||||
from yunohost.user import _get_user_for_ssh
|
||||
|
||||
|
||||
def ssh_authorized_keys_list(auth, username):
|
||||
user = _get_user_for_ssh(auth, username, ["homeDirectory"])
|
||||
if not user:
|
||||
raise Exception("User with username '%s' doesn't exists" % username)
|
||||
|
||||
authorized_keys_file = os.path.join(user["homeDirectory"][0], ".ssh", "authorized_keys")
|
||||
|
||||
if not os.path.exists(authorized_keys_file):
|
||||
return []
|
||||
|
||||
keys = []
|
||||
last_comment = ""
|
||||
for line in read_file(authorized_keys_file).split("\n"):
|
||||
# empty line
|
||||
if not line.strip():
|
||||
continue
|
||||
|
||||
if line.lstrip().startswith("#"):
|
||||
last_comment = line.lstrip().lstrip("#").strip()
|
||||
continue
|
||||
|
||||
# assuming a key per non empty line
|
||||
key = line.strip()
|
||||
keys.append({
|
||||
"key": key,
|
||||
"name": last_comment,
|
||||
})
|
||||
|
||||
last_comment = ""
|
||||
|
||||
return {"keys": keys}
|
||||
|
||||
|
||||
def ssh_authorized_keys_add(auth, username, key, comment):
|
||||
user = _get_user_for_ssh(auth, username, ["homeDirectory", "uid"])
|
||||
if not user:
|
||||
raise Exception("User with username '%s' doesn't exists" % username)
|
||||
|
||||
authorized_keys_file = os.path.join(user["homeDirectory"][0], ".ssh", "authorized_keys")
|
||||
|
||||
if not os.path.exists(authorized_keys_file):
|
||||
# ensure ".ssh" exists
|
||||
mkdir(os.path.join(user["homeDirectory"][0], ".ssh"),
|
||||
force=True, parents=True, uid=user["uid"][0])
|
||||
|
||||
# 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)
|
||||
|
||||
authorized_keys_content = read_file(authorized_keys_file)
|
||||
|
||||
authorized_keys_content += "\n"
|
||||
authorized_keys_content += "\n"
|
||||
|
||||
if comment and comment.strip():
|
||||
if not comment.lstrip().startswith("#"):
|
||||
comment = "# " + comment
|
||||
authorized_keys_content += comment.replace("\n", " ").strip()
|
||||
authorized_keys_content += "\n"
|
||||
|
||||
authorized_keys_content += key.strip()
|
||||
authorized_keys_content += "\n"
|
||||
|
||||
write_to_file(authorized_keys_file, authorized_keys_content)
|
||||
|
||||
|
||||
def ssh_authorized_keys_remove(auth, username, key):
|
||||
user = _get_user(auth, username, ["homeDirectory", "uid"])
|
||||
if not user:
|
||||
raise Exception("User with username '%s' doesn't exists" % username)
|
||||
|
||||
authorized_keys_file = os.path.join(user["homeDirectory"][0], ".ssh", "authorized_keys")
|
||||
|
||||
if not os.path.exists(authorized_keys_file):
|
||||
raise Exception("this key doesn't exists ({} dosesn't exists)".format(authorized_keys_file))
|
||||
|
||||
authorized_keys_content = read_file(authorized_keys_file)
|
||||
|
||||
if key not in authorized_keys_content:
|
||||
raise Exception("Key '{}' is not present in authorized_keys".format(key))
|
||||
|
||||
# don't delete the previous comment because we can't verify if it's legit
|
||||
|
||||
# this regex approach failed for some reasons and I don't know why :(
|
||||
# authorized_keys_content = re.sub("{} *\n?".format(key),
|
||||
# "",
|
||||
# authorized_keys_content,
|
||||
# flags=re.MULTILINE)
|
||||
|
||||
authorized_keys_content = authorized_keys_content.replace(key, "")
|
||||
|
||||
write_to_file(authorized_keys_file, authorized_keys_content)
|
|
@ -33,8 +33,9 @@ import logging
|
|||
import subprocess
|
||||
import pwd
|
||||
import socket
|
||||
from collections import OrderedDict
|
||||
from xmlrpclib import Fault
|
||||
from importlib import import_module
|
||||
from collections import OrderedDict
|
||||
|
||||
import apt
|
||||
import apt.progress
|
||||
|
@ -45,12 +46,13 @@ from moulinette.utils.log import getActionLogger
|
|||
from moulinette.utils.process import check_output
|
||||
from moulinette.utils.filesystem import read_json, write_to_json
|
||||
from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron
|
||||
from yunohost.domain import domain_add, domain_list, get_public_ip, _get_maindomain, _set_maindomain
|
||||
from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain
|
||||
from yunohost.dyndns import _dyndns_available, _dyndns_provides
|
||||
from yunohost.firewall import firewall_upnp
|
||||
from yunohost.service import service_status, service_regen_conf, service_log, service_start, service_enable
|
||||
from yunohost.monitor import monitor_disk, monitor_system
|
||||
from yunohost.utils.packages import ynh_packages_version
|
||||
from yunohost.utils.network import get_public_ip
|
||||
|
||||
# FIXME this is a duplicate from apps.py
|
||||
APPS_SETTING_PATH = '/etc/yunohost/apps/'
|
||||
|
@ -568,7 +570,7 @@ def tools_diagnosis(auth, private=False):
|
|||
diagnosis['system'] = OrderedDict()
|
||||
try:
|
||||
disks = monitor_disk(units=['filesystem'], human_readable=True)
|
||||
except MoulinetteError as e:
|
||||
except (MoulinetteError, Fault) as e:
|
||||
logger.warning(m18n.n('diagnosis_monitor_disk_error', error=format(e)), exc_info=1)
|
||||
else:
|
||||
diagnosis['system']['disks'] = {}
|
||||
|
@ -621,16 +623,11 @@ def tools_diagnosis(auth, private=False):
|
|||
# Private data
|
||||
if private:
|
||||
diagnosis['private'] = OrderedDict()
|
||||
|
||||
# Public IP
|
||||
diagnosis['private']['public_ip'] = {}
|
||||
try:
|
||||
diagnosis['private']['public_ip']['IPv4'] = get_public_ip(4)
|
||||
except MoulinetteError as e:
|
||||
pass
|
||||
try:
|
||||
diagnosis['private']['public_ip']['IPv6'] = get_public_ip(6)
|
||||
except MoulinetteError as e:
|
||||
pass
|
||||
diagnosis['private']['public_ip']['IPv4'] = get_public_ip(4)
|
||||
diagnosis['private']['public_ip']['IPv6'] = get_public_ip(6)
|
||||
|
||||
# Domains
|
||||
diagnosis['private']['domains'] = domain_list(auth)['domains']
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"""
|
||||
import os
|
||||
import re
|
||||
import pwd
|
||||
import json
|
||||
import errno
|
||||
import crypt
|
||||
|
@ -35,10 +36,13 @@ import subprocess
|
|||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.filesystem import read_file
|
||||
from yunohost.service import service_status
|
||||
|
||||
logger = getActionLogger('yunohost.user')
|
||||
|
||||
SSHD_CONFIG_PATH = "/etc/ssh/sshd_config"
|
||||
|
||||
|
||||
def user_list(auth, fields=None):
|
||||
"""
|
||||
|
@ -56,6 +60,8 @@ def user_list(auth, fields=None):
|
|||
'cn': 'fullname',
|
||||
'mail': 'mail',
|
||||
'maildrop': 'mail-forward',
|
||||
'loginShell': 'shell',
|
||||
'homeDirectory': 'home_path',
|
||||
'mailuserquota': 'mailbox-quota'
|
||||
}
|
||||
|
||||
|
@ -71,7 +77,7 @@ def user_list(auth, fields=None):
|
|||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('field_invalid', attr))
|
||||
else:
|
||||
attrs = ['uid', 'cn', 'mail', 'mailuserquota']
|
||||
attrs = ['uid', 'cn', 'mail', 'mailuserquota', 'loginShell']
|
||||
|
||||
result = auth.search('ou=users,dc=yunohost,dc=org',
|
||||
'(&(objectclass=person)(!(uid=root))(!(uid=nobody)))',
|
||||
|
@ -81,6 +87,12 @@ def user_list(auth, fields=None):
|
|||
entry = {}
|
||||
for attr, values in user.items():
|
||||
if values:
|
||||
if attr == "loginShell":
|
||||
if values[0].strip() == "/bin/false":
|
||||
entry["ssh_allowed"] = False
|
||||
else:
|
||||
entry["ssh_allowed"] = True
|
||||
|
||||
entry[user_attrs[attr]] = values[0]
|
||||
|
||||
uid = entry[user_attrs['uid']]
|
||||
|
@ -435,6 +447,36 @@ def user_info(auth, username):
|
|||
raise MoulinetteError(167, m18n.n('user_info_failed'))
|
||||
|
||||
|
||||
def user_allow_ssh(auth, username):
|
||||
"""
|
||||
Allow YunoHost user connect as ssh.
|
||||
|
||||
Keyword argument:
|
||||
username -- User 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))
|
||||
|
||||
auth.update('uid=%s,ou=users' % username, {'loginShell': '/bin/bash'})
|
||||
|
||||
|
||||
def user_disallow_ssh(auth, username):
|
||||
"""
|
||||
Disallow YunoHost user connect as ssh.
|
||||
|
||||
Keyword argument:
|
||||
username -- User 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))
|
||||
|
||||
auth.update('uid=%s,ou=users' % username, {'loginShell': '/bin/false'})
|
||||
|
||||
|
||||
def _convertSize(num, suffix=''):
|
||||
for unit in ['K', 'M', 'G', 'T', 'P', 'E', 'Z']:
|
||||
if abs(num) < 1024.0:
|
||||
|
@ -470,3 +512,56 @@ def _hash_user_password(password):
|
|||
|
||||
salt = '$6$' + salt + '$'
|
||||
return '{CRYPT}' + crypt.crypt(str(password), salt)
|
||||
|
||||
|
||||
def _get_user_for_ssh(auth, username, attrs=None):
|
||||
def ssh_root_login_status(auth):
|
||||
# XXX temporary placed here for when the ssh_root commands are integrated
|
||||
# extracted from https://github.com/YunoHost/yunohost/pull/345
|
||||
# XXX should we support all the options?
|
||||
# this is the content of "man sshd_config"
|
||||
# PermitRootLogin
|
||||
# Specifies whether root can log in using ssh(1). The argument must be
|
||||
# “yes”, “without-password”, “forced-commands-only”, or “no”. The
|
||||
# default is “yes”.
|
||||
sshd_config_content = read_file(SSHD_CONFIG_PATH)
|
||||
|
||||
if re.search("^ *PermitRootLogin +(no|forced-commands-only) *$",
|
||||
sshd_config_content, re.MULTILINE):
|
||||
return {"PermitRootLogin": False}
|
||||
|
||||
return {"PermitRootLogin": True}
|
||||
|
||||
if username == "root":
|
||||
root_unix = pwd.getpwnam("root")
|
||||
return {
|
||||
'username': 'root',
|
||||
'fullname': '',
|
||||
'mail': '',
|
||||
'ssh_allowed': ssh_root_login_status(auth)["PermitRootLogin"],
|
||||
'shell': root_unix.pw_shell,
|
||||
'home_path': root_unix.pw_dir,
|
||||
}
|
||||
|
||||
if username == "admin":
|
||||
admin_unix = pwd.getpwnam("admin")
|
||||
return {
|
||||
'username': 'admin',
|
||||
'fullname': '',
|
||||
'mail': '',
|
||||
'ssh_allowed': admin_unix.pw_shell.strip() != "/bin/false",
|
||||
'shell': admin_unix.pw_shell,
|
||||
'home_path': admin_unix.pw_dir,
|
||||
}
|
||||
|
||||
# TODO escape input using https://www.python-ldap.org/doc/html/ldap-filter.html
|
||||
user = auth.search('ou=users,dc=yunohost,dc=org',
|
||||
'(&(objectclass=person)(uid=%s))' % username,
|
||||
attrs)
|
||||
|
||||
assert len(user) in (0, 1)
|
||||
|
||||
if not user:
|
||||
return None
|
||||
|
||||
return user[0]
|
||||
|
|
39
src/yunohost/utils/network.py
Normal file
39
src/yunohost/utils/network.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" License
|
||||
|
||||
Copyright (C) 2017 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
|
||||
|
||||
"""
|
||||
import logging
|
||||
from urllib import urlopen
|
||||
|
||||
logger = logging.getLogger('yunohost.utils.network')
|
||||
|
||||
def get_public_ip(protocol=4):
|
||||
"""Retrieve the public IP address from ip.yunohost.org"""
|
||||
|
||||
if protocol == 4:
|
||||
url = 'https://ip.yunohost.org'
|
||||
elif protocol == 6:
|
||||
url = 'https://ip6.yunohost.org'
|
||||
else:
|
||||
raise ValueError("invalid protocol version")
|
||||
|
||||
try:
|
||||
return urlopen(url).read().strip()
|
||||
except IOError:
|
||||
return None
|
Loading…
Add table
Reference in a new issue