Manual merge with unstable

This commit is contained in:
Julien Malik 2016-03-31 17:20:11 +02:00
commit 3ef177ea3b
22 changed files with 387 additions and 1430 deletions

View file

@ -1,8 +1,13 @@
#!/usr/bin/env python #! /usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import sys
import os import os
import sys
import argparse
import moulinette
from moulinette.actionsmap import ActionsMap
from moulinette.interfaces.cli import colorize, get_locale
# Either we are in a development environment or not # Either we are in a development environment or not
IN_DEVEL = False IN_DEVEL = False
@ -34,17 +39,11 @@ if IN_DEVEL:
def _die(message, title='Error:'): def _die(message, title='Error:'):
"""Print error message and exit""" """Print error message and exit"""
try:
from moulinette.interfaces.cli import colorize
except ImportError:
colorize = lambda msg, c: msg
print('%s %s' % (colorize(title, 'red'), message)) print('%s %s' % (colorize(title, 'red'), message))
sys.exit(1) sys.exit(1)
def _parse_cli_args(): def _parse_cli_args():
"""Parse additional arguments for the cli""" """Parse additional arguments for the cli"""
import argparse
parser = argparse.ArgumentParser(add_help=False) parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--no-cache', parser.add_argument('--no-cache',
action='store_false', default=True, dest='use_cache', action='store_false', default=True, dest='use_cache',
@ -90,8 +89,6 @@ def _parse_cli_args():
def _init_moulinette(debug=False, verbose=False, quiet=False): def _init_moulinette(debug=False, verbose=False, quiet=False):
"""Configure logging and initialize the moulinette""" """Configure logging and initialize the moulinette"""
from moulinette import init
# Define loggers handlers # Define loggers handlers
handlers = set(LOGGERS_HANDLERS) handlers = set(LOGGERS_HANDLERS)
if quiet and 'tty' in handlers: if quiet and 'tty' in handlers:
@ -100,7 +97,7 @@ def _init_moulinette(debug=False, verbose=False, quiet=False):
handlers.append('tty') handlers.append('tty')
root_handlers = set(handlers) root_handlers = set(handlers)
if not debug: if not debug and 'tty' in root_handlers:
root_handlers.remove('tty') root_handlers.remove('tty')
# Define loggers level # Define loggers level
@ -167,11 +164,10 @@ def _init_moulinette(debug=False, verbose=False, quiet=False):
_die(str(e)) _die(str(e))
# Initialize moulinette # Initialize moulinette
init(logging_config=logging, _from_source=IN_DEVEL) moulinette.init(logging_config=logging, _from_source=IN_DEVEL)
def _retrieve_namespaces(): def _retrieve_namespaces():
"""Return the list of namespaces to load""" """Return the list of namespaces to load"""
from moulinette.actionsmap import ActionsMap
ret = ['yunohost'] ret = ['yunohost']
for n in ActionsMap.get_namespaces(): for n in ActionsMap.get_namespaces():
# Append YunoHost modules # Append YunoHost modules
@ -190,8 +186,6 @@ if __name__ == '__main__':
if not os.path.isfile('/etc/yunohost/installed') and \ if not os.path.isfile('/etc/yunohost/installed') and \
(len(args) < 2 or (args[0] +' '+ args[1] != 'tools postinstall' and \ (len(args) < 2 or (args[0] +' '+ args[1] != 'tools postinstall' and \
args[0] +' '+ args[1] != 'backup restore')): args[0] +' '+ args[1] != 'backup restore')):
from moulinette.interfaces.cli import get_locale
# Init i18n # Init i18n
m18n.load_namespace('yunohost') m18n.load_namespace('yunohost')
m18n.set_locale(get_locale()) m18n.set_locale(get_locale())
@ -200,9 +194,9 @@ if __name__ == '__main__':
_die(m18n.n('yunohost_not_installed'), m18n.g('error')) _die(m18n.n('yunohost_not_installed'), m18n.g('error'))
# Execute the action # Execute the action
from moulinette import cli ret = moulinette.cli(
ret = cli(_retrieve_namespaces(), args, _retrieve_namespaces(), args,
use_cache=opts.use_cache, output_as=opts.output_as, use_cache=opts.use_cache, output_as=opts.output_as,
password=opts.password, parser_kwargs={'top_parser': parser} password=opts.password, parser_kwargs={'top_parser': parser}
) )
sys.exit(ret) sys.exit(ret)

View file

@ -1,8 +1,13 @@
#!/usr/bin/env python #! /usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os
import sys import sys
import os.path import argparse
import moulinette
from moulinette.actionsmap import ActionsMap
from moulinette.interfaces.cli import colorize
# Either we are in a development environment or not # Either we are in a development environment or not
IN_DEVEL = False IN_DEVEL = False
@ -38,17 +43,11 @@ if IN_DEVEL:
def _die(message, title='Error:'): def _die(message, title='Error:'):
"""Print error message and exit""" """Print error message and exit"""
try:
from moulinette.interfaces.cli import colorize
except ImportError:
colorize = lambda msg, c: msg
print('%s %s' % (colorize(title, 'red'), message)) print('%s %s' % (colorize(title, 'red'), message))
sys.exit(1) sys.exit(1)
def _parse_api_args(): def _parse_api_args():
"""Parse main arguments for the api""" """Parse main arguments for the api"""
import argparse
parser = argparse.ArgumentParser(add_help=False, parser = argparse.ArgumentParser(add_help=False,
description="Run the YunoHost API to manage your server.", description="Run the YunoHost API to manage your server.",
) )
@ -87,8 +86,6 @@ def _parse_api_args():
def _init_moulinette(use_websocket=True, debug=False, verbose=False): def _init_moulinette(use_websocket=True, debug=False, verbose=False):
"""Configure logging and initialize the moulinette""" """Configure logging and initialize the moulinette"""
from moulinette import init
# Define loggers handlers # Define loggers handlers
handlers = set(LOGGERS_HANDLERS) handlers = set(LOGGERS_HANDLERS)
if not use_websocket and 'api' in handlers: if not use_websocket and 'api' in handlers:
@ -162,11 +159,10 @@ def _init_moulinette(use_websocket=True, debug=False, verbose=False):
_die(str(e)) _die(str(e))
# Initialize moulinette # Initialize moulinette
init(logging_config=logging, _from_source=IN_DEVEL) moulinette.init(logging_config=logging, _from_source=IN_DEVEL)
def _retrieve_namespaces(): def _retrieve_namespaces():
"""Return the list of namespaces to load""" """Return the list of namespaces to load"""
from moulinette.actionsmap import ActionsMap
ret = ['yunohost'] ret = ['yunohost']
for n in ActionsMap.get_namespaces(): for n in ActionsMap.get_namespaces():
# Append YunoHost modules # Append YunoHost modules
@ -195,14 +191,12 @@ if __name__ == '__main__':
_init_moulinette(opts.use_websocket, opts.debug, opts.verbose) _init_moulinette(opts.use_websocket, opts.debug, opts.verbose)
# Run the server # Run the server
from moulinette import api, MoulinetteError
from yunohost.utils.packages import ynh_packages_version from yunohost.utils.packages import ynh_packages_version
ret = api(_retrieve_namespaces(), ret = moulinette.api(
host=opts.host, port=opts.port, _retrieve_namespaces(),
routes={ host=opts.host, port=opts.port, routes={
('GET', '/installed'): is_installed, ('GET', '/installed'): is_installed,
('GET', '/version'): ynh_packages_version, ('GET', '/version'): ynh_packages_version,
}, }, use_cache=opts.use_cache, use_websocket=opts.use_websocket
use_cache=opts.use_cache, use_websocket=opts.use_websocket
) )
sys.exit(ret) sys.exit(ret)

View file

@ -334,7 +334,7 @@ app:
arguments: arguments:
-u: -u:
full: --url full: --url
help: URL of remote JSON list (default https://yunohost.org/official.json) help: URL of remote JSON list (default https://app.yunohost.org/official.json)
-n: -n:
full: --name full: --name
help: Name of the list (default yunohost) help: Name of the list (default yunohost)
@ -377,6 +377,14 @@ app:
full: --raw full: --raw
help: Return the full app_dict help: Return the full app_dict
action: store_true action: store_true
-i:
full: --installed
help: Return only installed apps
action: store_true
-b:
full: --with-backup
help: Return only apps with backup feature (force --installed filter)
action: store_true
### app_info() ### app_info()
info: info:
@ -428,7 +436,7 @@ app:
help: Custom name for the app help: Custom name for the app
-a: -a:
full: --args full: --args
help: Serialize arguments for app installation help: Serialized arguments for app script (i.e. "domain=domain.tld&path=/path")
### app_remove() TODO: Write help ### app_remove() TODO: Write help
remove: remove:
@ -639,6 +647,8 @@ backup:
action_help: Restore from a local backup archive action_help: Restore from a local backup archive
api: POST /backup/restore/<name> api: POST /backup/restore/<name>
configuration: configuration:
authenticate: all
authenticator: ldap-anonymous
lock: false lock: false
arguments: arguments:
name: name:
@ -969,7 +979,6 @@ service:
action_help: > action_help: >
Check if the specific file has been modified and display differences. Check if the specific file has been modified and display differences.
Stores the file hash in the services.yml file Stores the file hash in the services.yml file
api: PUT /services/safecopy
arguments: arguments:
new_conf_file: new_conf_file:
help: Path to the desired conf file help: Path to the desired conf file
@ -990,7 +999,6 @@ service:
action_help: > action_help: >
Check if the specific file has been modified before removing it. Check if the specific file has been modified before removing it.
Backup the file in /home/yunohost.backup Backup the file in /home/yunohost.backup
api: PUT /services/safecopy
arguments: arguments:
conf_file: conf_file:
help: Path to the targeted conf file help: Path to the targeted conf file
@ -1159,10 +1167,10 @@ dyndns:
arguments: arguments:
--dyn-host: --dyn-host:
help: Dynette DNS server to inform help: Dynette DNS server to inform
default: "dynhost.yunohost.org" default: "dyndns.yunohost.org"
-d: -d:
full: --domain full: --domain
help: Full domain to subscribe with help: Full domain to update
extra: extra:
pattern: *pattern_domain pattern: *pattern_domain
-k: -k:
@ -1314,7 +1322,6 @@ hook:
### hook_add() ### hook_add()
add: add:
action_help: Store hook script to filesystem action_help: Store hook script to filesystem
api: PUT /hook
arguments: arguments:
app: app:
help: App to link with help: App to link with
@ -1324,7 +1331,6 @@ hook:
### hook_remove() ### hook_remove()
remove: remove:
action_help: Remove hook scripts from filesystem action_help: Remove hook scripts from filesystem
api: DELETE /hook
arguments: arguments:
app: app:
help: Scripts related to app will be removed help: Scripts related to app will be removed
@ -1362,7 +1368,7 @@ hook:
### hook_callback() ### hook_callback()
callback: callback:
action_help: Execute all scripts binded to an action action_help: Execute all scripts binded to an action
api: GET /hooks api: POST /hooks/<action>
arguments: arguments:
action: action:
help: Action name help: Action name
@ -1378,7 +1384,6 @@ hook:
### hook_exec() ### hook_exec()
exec: exec:
action_help: Execute hook from a file with arguments action_help: Execute hook from a file with arguments
api: GET /hook
arguments: arguments:
path: path:
help: Path of the script to execute help: Path of the script to execute
@ -1392,3 +1397,6 @@ hook:
full: --no-trace full: --no-trace
help: Do not print each command that will be executed help: Do not print each command that will be executed
action: store_true action: store_true
-d:
full: --chdir
help: The directory from where the script will be executed

View file

@ -4,7 +4,7 @@
# | arg: app - the application id # | arg: app - the application id
# | arg: key - the setting to get # | arg: key - the setting to get
ynh_app_setting_get() { ynh_app_setting_get() {
sudo yunohost app setting "$1" "$2" --output-as plain sudo yunohost app setting "$1" "$2" --output-as plain --quiet
} }
# Set an application setting # Set an application setting
@ -14,7 +14,7 @@ ynh_app_setting_get() {
# | arg: key - the setting name to set # | arg: key - the setting name to set
# | arg: value - the setting value to set # | arg: value - the setting value to set
ynh_app_setting_set() { ynh_app_setting_set() {
sudo yunohost app setting "$1" "$2" -v "$3" sudo yunohost app setting "$1" "$2" -v "$3" --quiet
} }
# Delete an application setting # Delete an application setting
@ -23,5 +23,5 @@ ynh_app_setting_set() {
# | arg: app - the application id # | arg: app - the application id
# | arg: key - the setting to delete # | arg: key - the setting to delete
ynh_app_setting_delete() { ynh_app_setting_delete() {
sudo yunohost app setting -d "$1" "$2" sudo yunohost app setting -d "$1" "$2" --quiet
} }

View file

@ -14,8 +14,11 @@ function safe_copy () {
cd /usr/share/yunohost/templates/rspamd cd /usr/share/yunohost/templates/rspamd
# Copy Rspamd configuration # Create configuration directories
safe_copy metrics.conf /etc/rspamd/metrics.conf sudo mkdir -p /etc/rspamd/local.d /etc/rspamd/override.d
# Copy specific configuration to rewrite the defaults
safe_copy metrics.conf.local /etc/rspamd/local.d/metrics.conf
# Install Rspamd sieve script # Install Rspamd sieve script
safe_copy rspamd.sieve /etc/dovecot/global_script/rspamd.sieve safe_copy rspamd.sieve /etc/dovecot/global_script/rspamd.sieve

View file

@ -17,11 +17,11 @@ function safe_copy () {
cd /usr/share/yunohost/templates/dnsmasq cd /usr/share/yunohost/templates/dnsmasq
# Get IPv4 address # Get IPv4 address
ip=$(curl -s -4 https://ip.yunohost.org 2>/dev/null) ip=$(curl -s -4 https://ip.yunohost.org 2>/dev/null || true)
ynh_validate_ip4 $ip || ip='0.0.0.0' ynh_validate_ip4 $ip || ip='0.0.0.0'
# Get IPv6 IP address # Get IPv6 IP address
ipv6=$(curl -s -6 http://ip6.yunohost.org 2>/dev/null) ipv6=$(curl -s -6 http://ip6.yunohost.org 2>/dev/null || true)
ynh_validate_ip6 $ipv6 || ipv6='' ynh_validate_ip6 $ipv6 || ipv6=''
sudo mkdir -p /etc/dnsmasq.d sudo mkdir -p /etc/dnsmasq.d

View file

@ -29,11 +29,14 @@ server {
return 302 https://$http_host/yunohost/admin; return 302 https://$http_host/yunohost/admin;
} }
# Block crawlers bot
location /yunohost { location /yunohost {
if ($http_user_agent ~ (crawl|Googlebot|Slurp|spider|bingbot|tracker|click|parser|spider|facebookexternalhit) ) { # Block crawlers bot
return 403; if ($http_user_agent ~ (crawl|Googlebot|Slurp|spider|bingbot|tracker|click|parser|spider|facebookexternalhit) ) {
} return 403;
}
# Redirect most of 404 to maindomain.tld/yunohost/sso
access_by_lua_file /usr/share/ssowat/access.lua;
} }
include conf.d/yunohost_admin.conf.inc; include conf.d/yunohost_admin.conf.inc;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,8 @@
# Metrics settings
# This define overridden options.
actions {
reject = 21;
add_header = 8;
greylist = 4;
}

69
debian/changelog vendored
View file

@ -1,3 +1,66 @@
moulinette-yunohost (2.2.4) stable; urgency=low
[ Jérôme Lebleu ]
* [fix] Update first registered domain with DynDNS instead of current_host
* [fix] Set found private key and don't validate it in dyndns_update
* [fix] Use dyndns.yunohost.org instead of dynhost.yunohost.org
[ opi ]
* [fix] Catch ConnectionError from requests package
-- Jérôme Lebleu <jerome@yunohost.org> Sun, 27 Mar 2016 16:30:42 +0200
yunohost (2.3.11.2) testing; urgency=low
* [fix] Don't fail dnsmasq regen if IPv4/6 cannot be retrieved
-- Jérôme Lebleu <jerome@yunohost.org> Wed, 23 Mar 2016 14:57:22 +0100
yunohost (2.3.11.1) testing; urgency=low
* [deb] Include sysvinit services and files in the package, thanks to
nthykier and pabs from #debian-mentor
-- Jérôme Lebleu <jerome@yunohost.org> Wed, 23 Mar 2016 12:38:56 +0100
yunohost (2.3.11) testing; urgency=low
[ Laurent Peuch ]
* [mod] Explain how to start yunohost-firewall service
[ Jérôme Lebleu ]
* [fix] Remove useless API routes for some actions
* [fix] Update API route for hook_callback action
* [deb] Attempt to improve services management in Debian packaging
* [deb] Add missing cron dependency
* [deb] Clean debian/control with cosmetic changes
* [deb] Fix helpers bash script installation
[ Julien Malik ]
* [enh] Add helper for IP address validation
* [enh] move /usr/share/yunohost/apps/helpers to
/usr/share/yunohost/helpers since it became of more general use
* [enh] Remove unused checkupdate and upgrade scripts
* [fix] Validate IP addresses returned by ipX.yunohost.org
* [fix] fix lintian script-not-executable
* [deb] dh_python2 replaces shebang during build. Using the correct one
in source directly
[ Moul ]
* [enh] Add '-a' argument's usage example for app_install
[ opi ]
* [enh] Add diagnosis function. #39
* [enh] Redirect most of 404 to maindomain.tld/yunohost/sso
* [enh] Add --installed and --with-backup to app_list action (wip #227)
* [enh] More explicit backup forbidden directory error message.
* [enh] Use dedicated app list domain.
* [fix] Use only dyndns.yunohost.org domain.
* [fix] Use plain text 502 error page.
* [fix] Cleaner Nginx redirection rules. Use permanent only when paths match.
-- Jérôme Lebleu <jerome@yunohost.org> Wed, 23 Mar 2016 10:39:34 +0100
yunohost (2.3.10.2) testing; urgency=low yunohost (2.3.10.2) testing; urgency=low
* [fix] Workaround for the bad people who are not using IPv6 yet * [fix] Workaround for the bad people who are not using IPv6 yet
@ -332,12 +395,6 @@ moulinette-yunohost (2.3.0) testing; urgency=low
-- Jérôme Lebleu <jerome.lebleu@mailoo.org> Tue, 08 Sep 2015 14:19:28 +0200 -- Jérôme Lebleu <jerome.lebleu@mailoo.org> Tue, 08 Sep 2015 14:19:28 +0200
moulinette-yunohost (2.2.3-1) stable; urgency=low
* [fix] Catch ConnectionError from requests package
-- opi <opi@zeropi.net> Sun, 06 Mar 2016 21:51:06 +0100
moulinette-yunohost (2.2.3) stable; urgency=low moulinette-yunohost (2.2.3) stable; urgency=low
* [fix] Catch proper exception in backup_list (fix #65) * [fix] Catch proper exception in backup_list (fix #65)

77
debian/control vendored
View file

@ -9,50 +9,41 @@ Homepage: https://yunohost.org/
Package: yunohost Package: yunohost
Architecture: all Architecture: all
Depends: ${python:Depends}, ${misc:Depends}, Depends: ${python:Depends}, ${misc:Depends}
moulinette (>= 2.3.4), , moulinette (>= 2.3.4)
python-psutil, , python-psutil, python-requests, python-dnspython
python-requests, , python-apt, python-miniupnpc
glances, , glances
python-pip, , dnsutils, bind9utils, unzip, git, curl, cron
python-miniupnpc | pyminiupnpc, , ca-certificates, netcat-openbsd, iproute
dnsutils, , mariadb-server | mysql-server, php5-mysql | php5-mysqlnd
bind9utils, , slapd, ldap-utils, sudo-ldap, libnss-ldapd
python-apt, , postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail
ca-certificates, , dovecot-ldap, dovecot-lmtpd, dovecot-managesieved
python-dnspython, , dovecot-antispam, fail2ban
netcat-openbsd, , nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl
iproute, , dnsmasq, openssl, avahi-daemon
unzip, , ssowat, metronome
git-core, , rspamd (>= 1.2.0), rmilter (>=1.7.0), redis-server, opendkim-tools
curl, Recommends: yunohost-admin
mariadb-server | mysql-server, php5-mysql | php5-mysqlnd, , openssh-server, ntp, inetutils-ping | iputils-ping
slapd, ldap-utils, sudo-ldap, libnss-ldapd, , bash-completion, rsyslog
postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail, , php5-gd, php5-curl, php-gettext, php5-mcrypt
dovecot-ldap, dovecot-lmtpd, dovecot-managesieved, , python-pip
dovecot-antispam, fail2ban, , unattended-upgrades
nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl, , libdbd-ldap-perl, libnet-dns-perl
dnsmasq, openssl, avahi-daemon,
ssowat, metronome,
rspamd, rmilter (>=1.7.0), redis-server, opendkim-tools
Recommends: yunohost-admin,
bash-completion, rsyslog, ntp, openssh-server,
inetutils-ping | iputils-ping,
php5-gd, php5-curl, php-gettext, php5-mcrypt,
unattended-upgrades,
libdbd-ldap-perl, libnet-dns-perl
Suggests: htop, vim, rsync, acpi-support-base, udisks2 Suggests: htop, vim, rsync, acpi-support-base, udisks2
Conflicts: iptables-persistent, Conflicts: iptables-persistent
moulinette-yunohost, yunohost-config, , moulinette-yunohost, yunohost-config
yunohost-config-others, yunohost-config-postfix, , yunohost-config-others, yunohost-config-postfix
yunohost-config-dovecot, yunohost-config-slapd, , yunohost-config-dovecot, yunohost-config-slapd
yunohost-config-nginx, yunohost-config-amavis, , yunohost-config-nginx, yunohost-config-amavis
yunohost-config-mysql, yunohost-predepends , yunohost-config-mysql, yunohost-predepends
Replaces: moulinette-yunohost, yunohost-config, Replaces: moulinette-yunohost, yunohost-config
yunohost-config-others, yunohost-config-postfix, , yunohost-config-others, yunohost-config-postfix
yunohost-config-dovecot, yunohost-config-slapd, , yunohost-config-dovecot, yunohost-config-slapd
yunohost-config-nginx, yunohost-config-amavis, , yunohost-config-nginx, yunohost-config-amavis
yunohost-config-mysql, yunohost-predepends , yunohost-config-mysql, yunohost-predepends
Description: manageable and configured self-hosting server Description: manageable and configured self-hosting server
YunoHost aims to make self-hosting accessible to everyone. It configures YunoHost aims to make self-hosting accessible to everyone. It configures
an email, Web and IM server alongside a LDAP base. It also provides an email, Web and IM server alongside a LDAP base. It also provides

3
debian/rules vendored
View file

@ -8,7 +8,8 @@
dh ${@} --with=python2,systemd dh ${@} --with=python2,systemd
override_dh_installinit: override_dh_installinit:
dh_installinit --noscripts dh_installinit -pyunohost --name=yunohost-api --noscripts
dh_installinit -pyunohost --name=yunohost-firewall --noscripts
override_dh_systemd_enable: override_dh_systemd_enable:
dh_systemd_enable --name=yunohost-api dh_systemd_enable --name=yunohost-api

View file

@ -5,7 +5,7 @@
"installation_complete" : "Installation complete", "installation_complete" : "Installation complete",
"installation_failed" : "Installation failed", "installation_failed" : "Installation failed",
"unexpected_error" : "An unexpected error occured", "unexpected_error" : "An unexpected error occured",
"action_invalid" : "Invalid action '{:s}'", "action_invalid" : "Invalid action '{action:s}'",
"not_enough_disk_space" : "Not enough free disk space on '{path:s}'", "not_enough_disk_space" : "Not enough free disk space on '{path:s}'",
"license_undefined" : "undefined", "license_undefined" : "undefined",
@ -27,6 +27,7 @@
"app_upgrade_failed" : "Unable to upgrade {app:s}", "app_upgrade_failed" : "Unable to upgrade {app:s}",
"app_id_invalid" : "Invalid app id", "app_id_invalid" : "Invalid app id",
"app_already_installed" : "{app:s} is already installed", "app_already_installed" : "{app:s} is already installed",
"app_not_properly_removed" : "{app:s} has not been properly removed",
"app_removed" : "{app:s} successfully removed", "app_removed" : "{app:s} successfully removed",
"app_location_already_used" : "An app is already installed on this location", "app_location_already_used" : "An app is already installed on this location",
"app_location_install_failed" : "Unable to install the app on this location", "app_location_install_failed" : "Unable to install the app on this location",
@ -68,8 +69,10 @@
"no_ipv6_connectivity": "IPv6 connectivity is not available", "no_ipv6_connectivity": "IPv6 connectivity is not available",
"dyndns_key_generating" : "DNS key is being generated, it may take a while...", "dyndns_key_generating" : "DNS key is being generated, it may take a while...",
"dyndns_key_not_found" : "DNS key not found for the domain",
"dyndns_no_domain_registered": "No domain has been registered with DynDNS",
"dyndns_unavailable" : "Unavailable DynDNS subdomain", "dyndns_unavailable" : "Unavailable DynDNS subdomain",
"dyndns_registration_failed" : "Unable to register DynDNS domain: {:s}", "dyndns_registration_failed" : "Unable to register DynDNS domain: {error:s}",
"dyndns_registered" : "DynDNS domain successfully registered", "dyndns_registered" : "DynDNS domain successfully registered",
"dyndns_ip_update_failed" : "Unable to update IP address on DynDNS", "dyndns_ip_update_failed" : "Unable to update IP address on DynDNS",
"dyndns_ip_updated" : "IP address successfully updated on DynDNS", "dyndns_ip_updated" : "IP address successfully updated on DynDNS",
@ -79,8 +82,8 @@
"port_available" : "Port {port:d} is available", "port_available" : "Port {port:d} is available",
"port_unavailable" : "Port {port:d} is not available", "port_unavailable" : "Port {port:d} is not available",
"port_already_opened" : "Port {} is already opened for {:s} connections", "port_already_opened" : "Port {port:d} is already opened for {ip_version:s} connections",
"port_already_closed" : "Port {} is already closed for {:s} connections", "port_already_closed" : "Port {port:d} is already closed for {ip_version:s} connections",
"iptables_unavailable" : "You cannot play with iptables here. You are either in a container or your kernel does not support it.", "iptables_unavailable" : "You cannot play with iptables here. You are either in a container or your kernel does not support it.",
"ip6tables_unavailable" : "You cannot play with ip6tables here. You are either in a container or your kernel does not support it.", "ip6tables_unavailable" : "You cannot play with ip6tables here. You are either in a container or your kernel does not support it.",
"upnp_dev_not_found" : "No UPnP device found", "upnp_dev_not_found" : "No UPnP device found",
@ -92,12 +95,12 @@
"firewall_reloaded" : "Firewall successfully reloaded", "firewall_reloaded" : "Firewall successfully reloaded",
"hook_list_by_invalid" : "Invalid property to list hook by", "hook_list_by_invalid" : "Invalid property to list hook by",
"hook_name_unknown" : "Unknown hook name '{:s}'", "hook_name_unknown" : "Unknown hook name '{name:s}'",
"hook_exec_failed" : "Script execution failed", "hook_exec_failed" : "Script execution failed: {path:s}",
"hook_exec_not_terminated" : "Script execution hasnt terminated", "hook_exec_not_terminated" : "Script execution hasnt terminated: {path:s}",
"mountpoint_unknown" : "Unknown mountpoint", "mountpoint_unknown" : "Unknown mountpoint",
"unit_unknown" : "Unknown unit '{:s}'", "unit_unknown" : "Unknown unit '{unit:s}'",
"monitor_period_invalid" : "Invalid time period", "monitor_period_invalid" : "Invalid time period",
"monitor_stats_no_update" : "No monitoring statistics to update", "monitor_stats_no_update" : "No monitoring statistics to update",
"monitor_stats_file_not_found" : "Statistics file not found", "monitor_stats_file_not_found" : "Statistics file not found",
@ -107,25 +110,26 @@
"monitor_not_enabled" : "Server monitoring is not enabled", "monitor_not_enabled" : "Server monitoring is not enabled",
"monitor_glances_con_failed" : "Unable to connect to Glances server", "monitor_glances_con_failed" : "Unable to connect to Glances server",
"service_unknown" : "Unknown service '{:s}'", "service_unknown" : "Unknown service '{service:s}'",
"service_add_failed" : "Unable to add service '{:s}'", "service_add_failed" : "Unable to add service '{service:s}'",
"service_added" : "Service successfully added", "service_added" : "Service successfully added: '{service:s}'",
"service_remove_failed" : "Unable to remove service '{:s}'", "service_remove_failed" : "Unable to remove service '{service:s}'",
"service_removed" : "Service successfully removed", "service_removed" : "Service successfully removed: '{service:s}'",
"service_start_failed" : "Unable to start service '{:s}'", "service_start_failed" : "Unable to start service '{service:s}'",
"service_already_started" : "Service '{:s}' is already started", "service_already_started" : "Service '{service:s}' is already started",
"service_started" : "Service '{:s}' successfully started", "service_started" : "Service '{service:s}' successfully started",
"service_stop_failed" : "Unable to stop service '{:s}'", "service_stop_failed" : "Unable to stop service '{service:s}'",
"service_already_stopped" : "Service '{:s}' is already stopped", "service_already_stopped" : "Service '{service:s}' is already stopped",
"service_stopped" : "Service '{:s}' successfully stopped", "service_stopped" : "Service '{service:s}' successfully stopped",
"service_enable_failed" : "Unable to enable service '{:s}'", "service_enable_failed" : "Unable to enable service '{service:s}'",
"service_enabled" : "Service '{:s}' successfully enabled", "service_enabled" : "Service '{service:s}' successfully enabled",
"service_disable_failed" : "Unable to disable service '{:s}'", "service_disable_failed" : "Unable to disable service '{service:s}'",
"service_disabled" : "Service '{:s}' successfully disabled", "service_disabled" : "Service '{service:s}' successfully disabled",
"service_status_failed" : "Unable to determine status of service '{:s}'", "service_status_failed" : "Unable to determine status of service '{service:s}'",
"service_no_log" : "No log to display for service '{:s}'", "service_no_log" : "No log to display for service '{service:s}'",
"service_cmd_exec_failed" : "Unable to execute command '{:s}'", "service_cmd_exec_failed" : "Unable to execute command '{command:s}'",
"services_configured": "Configuration successfully generated", "service_configured": "Configuration successfully generated for service '{service:s}'",
"service_configured_all": "Configuration successfully generated for every services",
"service_configuration_conflict": "The file {file:s} has been changed since its last generation. Please apply the modifications manually or use the option --force (it will erase all the modifications previously done to the file).", "service_configuration_conflict": "The file {file:s} has been changed since its last generation. Please apply the modifications manually or use the option --force (it will erase all the modifications previously done to the file).",
"no_such_conf_file": "Unable to copy the file {file:s}: the file does not exist", "no_such_conf_file": "Unable to copy the file {file:s}: the file does not exist",
"service_add_configuration": "Adding the configuration file {file:s}", "service_add_configuration": "Adding the configuration file {file:s}",
@ -148,14 +152,14 @@
"updating_apt_cache" : "Updating the lists of available packages...", "updating_apt_cache" : "Updating the lists of available packages...",
"update_cache_failed" : "Unable to update APT cache", "update_cache_failed" : "Unable to update APT cache",
"packages_no_upgrade" : "There is no package to upgrade", "packages_no_upgrade" : "There is no package to upgrade",
"packages_upgrade_critical_later" : "Critical packages ({:s}) will be upgraded later", "packages_upgrade_critical_later" : "Critical packages ({packages:s}) will be upgraded later",
"upgrading_packages" : "Upgrading packages...", "upgrading_packages" : "Upgrading packages...",
"packages_upgrade_failed" : "Unable to upgrade all packages", "packages_upgrade_failed" : "Unable to upgrade all packages",
"system_upgraded" : "System successfully upgraded", "system_upgraded" : "System successfully upgraded",
"backup_action_required" : "You must specify something to save", "backup_action_required" : "You must specify something to save",
"backup_output_directory_required" : "You must provide an output directory for the backup", "backup_output_directory_required" : "You must provide an output directory for the backup",
"backup_output_directory_forbidden" : "Forbidden output directory", "backup_output_directory_forbidden" : "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders.",
"backup_output_directory_not_empty" : "Output directory is not empty", "backup_output_directory_not_empty" : "Output directory is not empty",
"backup_hook_unknown" : "Backup hook '{hook:s}' unknown", "backup_hook_unknown" : "Backup hook '{hook:s}' unknown",
"backup_running_hooks" : "Running backup hooks...", "backup_running_hooks" : "Running backup hooks...",
@ -190,10 +194,10 @@
"backup_deleted" : "Backup successfully deleted", "backup_deleted" : "Backup successfully deleted",
"field_invalid" : "Invalid field '{:s}'", "field_invalid" : "Invalid field '{:s}'",
"mail_domain_unknown" : "Unknown mail address domain '{:s}'", "mail_domain_unknown" : "Unknown mail address domain '{domain:s}'",
"mail_alias_remove_failed" : "Unable to remove mail alias '{:s}'", "mail_alias_remove_failed" : "Unable to remove mail alias '{mail:s}'",
"mail_forward_remove_failed" : "Unable to remove mail forward '{:s}'", "mail_forward_remove_failed" : "Unable to remove mail forward '{mail:s}'",
"user_unknown" : "Unknown user", "user_unknown" : "Unknown user: {user:s}",
"system_username_exists" : "Username already exists in the system users", "system_username_exists" : "Username already exists in the system users",
"user_creation_failed" : "Unable to create user", "user_creation_failed" : "Unable to create user",
"user_created" : "User successfully created", "user_created" : "User successfully created",

View file

@ -85,7 +85,7 @@ def app_fetchlist(url=None, name=None):
Keyword argument: Keyword argument:
name -- Name of the list (default yunohost) name -- Name of the list (default yunohost)
url -- URL of remote JSON list (default https://yunohost.org/official.json) url -- URL of remote JSON list (default https://app.yunohost.org/official.json)
""" """
# Create app path if not exists # Create app path if not exists
@ -93,7 +93,7 @@ def app_fetchlist(url=None, name=None):
except OSError: os.makedirs(repo_path) except OSError: os.makedirs(repo_path)
if url is None: if url is None:
url = 'https://yunohost.org/official.json' url = 'https://app.yunohost.org/official.json'
name = 'yunohost' name = 'yunohost'
else: else:
if name is None: if name is None:
@ -131,7 +131,7 @@ def app_removelist(name):
logger.success(m18n.n('appslist_removed')) logger.success(m18n.n('appslist_removed'))
def app_list(offset=None, limit=None, filter=None, raw=False): def app_list(offset=None, limit=None, filter=None, raw=False, installed=False, with_backup=False):
""" """
List apps List apps
@ -140,12 +140,15 @@ def app_list(offset=None, limit=None, filter=None, raw=False):
offset -- Starting number for app fetching offset -- Starting number for app fetching
limit -- Maximum number of app fetched limit -- Maximum number of app fetched
raw -- Return the full app_dict raw -- Return the full app_dict
installed -- Return only installed apps
with_backup -- Return only apps with backup feature (force --installed filter)
""" """
if offset: offset = int(offset) if offset: offset = int(offset)
else: offset = 0 else: offset = 0
if limit: limit = int(limit) if limit: limit = int(limit)
else: limit = 1000 else: limit = 1000
installed = with_backup or installed
app_dict = {} app_dict = {}
if raw: if raw:
@ -188,16 +191,27 @@ def app_list(offset=None, limit=None, filter=None, raw=False):
for app_id, app_info_dict in sorted_app_dict.items(): for app_id, app_info_dict in sorted_app_dict.items():
if i < limit: if i < limit:
if (filter and ((filter in app_id) or (filter in app_info_dict['manifest']['name']))) or not filter: if (filter and ((filter in app_id) or (filter in app_info_dict['manifest']['name']))) or not filter:
installed = _is_installed(app_id) app_installed = _is_installed(app_id)
# Only installed apps filter
if installed and not app_installed:
continue
# Filter only apps with backup and restore scripts
if with_backup and (
not os.path.isfile(apps_setting_path + app_id + '/scripts/backup') or
not os.path.isfile(apps_setting_path + app_id + '/scripts/restore')
):
continue
if raw: if raw:
app_info_dict['installed'] = installed app_info_dict['installed'] = app_installed
if installed: if app_installed:
app_info_dict['status'] = _get_app_status(app_id) app_info_dict['status'] = _get_app_status(app_id)
list_dict[app_id] = app_info_dict list_dict[app_id] = app_info_dict
else: else:
label = None label = None
if installed: if app_installed:
app_info_dict_raw = app_info(app=app_id, raw=True) app_info_dict_raw = app_info(app=app_id, raw=True)
label = app_info_dict_raw['settings']['label'] label = app_info_dict_raw['settings']['label']
list_dict.append({ list_dict.append({
@ -209,7 +223,7 @@ def app_list(offset=None, limit=None, filter=None, raw=False):
# FIXME: Temporarly allow undefined license # FIXME: Temporarly allow undefined license
'license': app_info_dict['manifest'].get('license', 'license': app_info_dict['manifest'].get('license',
m18n.n('license_undefined')), m18n.n('license_undefined')),
'installed': installed 'installed': app_installed
}) })
i += 1 i += 1
else: else:
@ -515,37 +529,53 @@ def app_install(auth, app, label=None, args=None):
# Move scripts and manifest to the right place # Move scripts and manifest to the right place
os.system('cp %s/manifest.json %s' % (app_tmp_folder, app_setting_path)) os.system('cp %s/manifest.json %s' % (app_tmp_folder, app_setting_path))
os.system('cp -R %s/scripts %s' % (app_tmp_folder, app_setting_path)) os.system('cp -R %s/scripts %s' % (app_tmp_folder, app_setting_path))
# Execute the app install script
install_retcode = 1
try: try:
if hook_exec(app_tmp_folder + '/scripts/install', args=args_list, env=env_dict) == 0: install_retcode = hook_exec(
# Store app status os.path.join(app_tmp_folder, 'scripts/install'), args_list, env=env_dict)
with open(app_setting_path + '/status.json', 'w+') as f: except (KeyboardInterrupt, EOFError):
json.dump(status, f) install_retcode = -1
# Clean and set permissions
shutil.rmtree(app_tmp_folder)
os.system('chmod -R 400 %s' % app_setting_path)
os.system('chown -R root: %s' % app_setting_path)
os.system('chown -R admin: %s/scripts' % app_setting_path)
app_ssowatconf(auth)
logger.success(m18n.n('installation_complete'))
else:
raise MoulinetteError(errno.EIO, m18n.n('installation_failed'))
except: except:
# Execute remove script and clean folders logger.exception(m18n.n('unexpected_error'))
hook_remove(app_instance_name) finally:
shutil.rmtree(app_setting_path) if install_retcode != 0:
shutil.rmtree(app_tmp_folder) # Setup environment for remove script
env_dict_remove = {}
env_dict_remove["YNH_APP_ID"] = app_id
env_dict_remove["YNH_APP_INSTANCE_NAME"] = app_instance_name
env_dict_remove["YNH_APP_INSTANCE_NUMBER"] = str(instance_number)
# Reraise proper exception # Execute remove script
try: remove_retcode = hook_exec(
raise os.path.join(app_tmp_folder, 'scripts/remove'), args=[app_instance_name], env=env_dict_remove)
except MoulinetteError: if remove_retcode != 0:
raise logger.warning(m18n.n('app_not_properly_removed', app=app_instance_name))
except (KeyboardInterrupt, EOFError):
raise MoulinetteError(errno.EINTR, m18n.g('operation_interrupted')) # Clean tmp folders
except Exception as e: hook_remove(app_instance_name)
logger.debug('app installation failed', exc_info=1) shutil.rmtree(app_setting_path)
raise MoulinetteError(errno.EIO, m18n.n('unexpected_error')) shutil.rmtree(app_tmp_folder)
if install_retcode == -1:
raise MoulinetteError(errno.EINTR,
m18n.g('operation_interrupted'))
raise MoulinetteError(errno.EIO, m18n.n('installation_failed'))
# Store app status
with open(app_setting_path + '/status.json', 'w+') as f:
json.dump(status, f)
# Clean and set permissions
shutil.rmtree(app_tmp_folder)
os.system('chmod -R 400 %s' % app_setting_path)
os.system('chown -R root: %s' % app_setting_path)
os.system('chown -R admin: %s/scripts' % app_setting_path)
app_ssowatconf(auth)
logger.success(m18n.n('installation_complete'))
def app_remove(auth, app): def app_remove(auth, app):
@ -624,10 +654,7 @@ def app_addaccess(auth, apps, users=[]):
try: try:
user_info(auth, allowed_user) user_info(auth, allowed_user)
except MoulinetteError: except MoulinetteError:
# FIXME: Add username keyword in user_unknown logger.warning(m18n.n('user_unknown', user=allowed_user))
logger.warning('{0}{1}'.format(
m18n.g('colon', m18n.n('user_unknown')),
allowed_user))
continue continue
allowed_users.add(allowed_user) allowed_users.add(allowed_user)

View file

@ -39,6 +39,13 @@ from moulinette.core import MoulinetteError
from moulinette.utils import filesystem from moulinette.utils import filesystem
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from yunohost.app import app_info, app_ssowatconf, _is_installed, _parse_app_instance_name
from yunohost.hook import (
hook_info, hook_callback, hook_exec, custom_hook_folder
)
from yunohost.monitor import binary_to_human
from yunohost.tools import tools_postinstall
backup_path = '/home/yunohost.backup' backup_path = '/home/yunohost.backup'
archives_path = '%s/archives' % backup_path archives_path = '%s/archives' % backup_path
@ -63,8 +70,6 @@ def backup_create(name=None, description=None, output_directory=None,
""" """
# TODO: Add a 'clean' argument to clean output directory # TODO: Add a 'clean' argument to clean output directory
from yunohost.hook import hook_info, hook_callback, hook_exec
tmp_dir = None tmp_dir = None
# Validate what to backup # Validate what to backup
@ -167,9 +172,6 @@ def backup_create(name=None, description=None, output_directory=None,
# Backup apps # Backup apps
if not ignore_apps: if not ignore_apps:
from yunohost.app import app_info
from yunohost.app import _parse_app_instance_name
# Filter applications to backup # Filter applications to backup
apps_list = set(os.listdir('/etc/yunohost/apps')) apps_list = set(os.listdir('/etc/yunohost/apps'))
apps_filtered = set() apps_filtered = set()
@ -215,7 +217,8 @@ def backup_create(name=None, description=None, output_directory=None,
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
hook_exec(tmp_script, args=[tmp_app_bkp_dir, app_instance_name], hook_exec(tmp_script, args=[tmp_app_bkp_dir, app_instance_name],
raise_on_error=True, env=env_dict) raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict)
except: except:
logger.exception(m18n.n('backup_app_failed', app=app_instance_name)) logger.exception(m18n.n('backup_app_failed', app=app_instance_name))
# Cleaning app backup directory # Cleaning app backup directory
@ -288,21 +291,20 @@ def backup_create(name=None, description=None, output_directory=None,
return { 'archive': info } return { 'archive': info }
def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=False, force=False): def backup_restore(auth, name, hooks=[], ignore_hooks=False,
apps=[], ignore_apps=False, force=False):
""" """
Restore from a local backup archive Restore from a local backup archive
Keyword argument: Keyword argument:
name -- Name of the local backup archive name -- Name of the local backup archive
hooks -- List of restoration hooks names to execute hooks -- List of restoration hooks names to execute
ignore_hooks -- Do not execute backup hooks
apps -- List of application names to restore apps -- List of application names to restore
ignore_apps -- Do not restore apps ignore_apps -- Do not restore apps
force -- Force restauration on an already installed system force -- Force restauration on an already installed system
""" """
from yunohost.hook import hook_info, hook_callback, hook_exec
from yunohost.hook import custom_hook_folder
# Validate what to restore # Validate what to restore
if ignore_hooks and ignore_apps: if ignore_hooks and ignore_apps:
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
@ -380,8 +382,6 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals
_clean_tmp_dir() _clean_tmp_dir()
raise MoulinetteError(errno.EEXIST, m18n.n('restore_failed')) raise MoulinetteError(errno.EEXIST, m18n.n('restore_failed'))
else: else:
from yunohost.tools import tools_postinstall
# Retrieve the domain from the backup # Retrieve the domain from the backup
try: try:
with open("%s/yunohost/current_host" % tmp_dir, 'r') as f: with open("%s/yunohost/current_host" % tmp_dir, 'r') as f:
@ -437,9 +437,6 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals
# Add apps restore hook # Add apps restore hook
if not ignore_apps: if not ignore_apps:
from yunohost.app import _is_installed
from yunohost.app import _parse_app_instance_name
# Filter applications to restore # Filter applications to restore
apps_list = set(info['apps'].keys()) apps_list = set(info['apps'].keys())
apps_filtered = set() apps_filtered = set()
@ -454,6 +451,7 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals
for app_instance_name in apps_filtered: for app_instance_name in apps_filtered:
tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_instance_name) tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_instance_name)
tmp_app_bkp_dir = tmp_app_dir + '/backup'
# Check if the app is not already installed # Check if the app is not already installed
if _is_installed(app_instance_name): if _is_installed(app_instance_name):
@ -487,7 +485,7 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb) env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
hook_exec(tmp_script, args=[tmp_app_dir + '/backup', app_instance_name], hook_exec(tmp_script, args=[tmp_app_dir + '/backup', app_instance_name],
raise_on_error=True, env=env_dict) raise_on_error=True, chdir=tmp_app_bkp_dir, env=env_dict)
except: except:
logger.exception(m18n.n('restore_app_failed', app=app_instance_name)) logger.exception(m18n.n('restore_app_failed', app=app_instance_name))
# Cleaning app directory # Cleaning app directory
@ -501,6 +499,8 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals
if not result['hooks'] and not result['apps']: if not result['hooks'] and not result['apps']:
_clean_tmp_dir(1) _clean_tmp_dir(1)
raise MoulinetteError(errno.EINVAL, m18n.n('restore_nothings_done')) raise MoulinetteError(errno.EINVAL, m18n.n('restore_nothings_done'))
if result['apps']:
app_ssowatconf(auth)
_clean_tmp_dir() _clean_tmp_dir()
logger.success(m18n.n('restore_complete')) logger.success(m18n.n('restore_complete'))
@ -553,8 +553,6 @@ def backup_info(name, with_details=False, human_readable=False):
human_readable -- Print sizes in human readable format human_readable -- Print sizes in human readable format
""" """
from yunohost.monitor import binary_to_human
archive_file = '%s/%s.tar.gz' % (archives_path, name) archive_file = '%s/%s.tar.gz' % (archives_path, name)
if not os.path.isfile(archive_file): if not os.path.isfile(archive_file):
raise MoulinetteError(errno.EIO, raise MoulinetteError(errno.EIO,
@ -602,7 +600,6 @@ def backup_delete(name):
name -- Name of the local backup archive name -- Name of the local backup archive
""" """
from yunohost.hook import hook_callback
hook_callback('pre_backup_delete', args=[name]) hook_callback('pre_backup_delete', args=[name])
archive_file = '%s/%s.tar.gz' % (archives_path, name) archive_file = '%s/%s.tar.gz' % (archives_path, name)

View file

@ -24,12 +24,12 @@
Subscribe and Update DynDNS Hosts Subscribe and Update DynDNS Hosts
""" """
import os import os
import requests
import re import re
import json import json
import glob import glob
import base64 import base64
import errno import errno
import requests
import subprocess import subprocess
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
@ -62,6 +62,10 @@ class IPRouteLine(object):
for k, v in self.m.groupdict().items(): for k, v in self.m.groupdict().items():
setattr(self, k, v) setattr(self, k, v)
re_dyndns_private_key = re.compile(
r'.*/K(?P<domain>[^\s\+]+)\.\+157.+\.private$'
)
def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None): def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None):
""" """
@ -107,30 +111,26 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None
try: error = json.loads(r.text)['error'] try: error = json.loads(r.text)['error']
except: error = "Server error" except: error = "Server error"
raise MoulinetteError(errno.EPERM, raise MoulinetteError(errno.EPERM,
m18n.n('dyndns_registration_failed', error)) m18n.n('dyndns_registration_failed', error=error))
logger.success(m18n.n('dyndns_registered')) logger.success(m18n.n('dyndns_registered'))
dyndns_installcron() dyndns_installcron()
def dyndns_update(dyn_host="dynhost.yunohost.org", domain=None, key=None, def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None,
ipv4=None, ipv6=None): ipv4=None, ipv6=None):
""" """
Update IP on DynDNS platform Update IP on DynDNS platform
Keyword argument: Keyword argument:
domain -- Full domain to subscribe with domain -- Full domain to update
dyn_host -- Dynette DNS server to inform dyn_host -- Dynette DNS server to inform
key -- Public DNS key key -- Public DNS key
ipv4 -- IP address to send ipv4 -- IP address to send
ipv6 -- IPv6 address to send ipv6 -- IPv6 address to send
""" """
if domain is None:
with open('/etc/yunohost/current_host', 'r') as f:
domain = f.readline().rstrip()
# IPv4 # IPv4
if ipv4 is None: if ipv4 is None:
ipv4 = get_public_ip() ipv4 = get_public_ip()
@ -168,6 +168,37 @@ def dyndns_update(dyn_host="dynhost.yunohost.org", domain=None, key=None,
old_ipv6 = '0000:0000:0000:0000:0000:0000:0000:0000' old_ipv6 = '0000:0000:0000:0000:0000:0000:0000:0000'
if old_ip != ipv4 or old_ipv6 != ipv6: if old_ip != ipv4 or old_ipv6 != ipv6:
if domain is None:
# Retrieve the first registered domain
for path in glob.iglob('/etc/yunohost/dyndns/K*.private'):
match = re_dyndns_private_key.match(path)
if not match:
continue
_domain = match.group('domain')
try:
# Check if domain is registered
if requests.get('https://{0}/test/{1}'.format(
dyn_host, _domain)).status_code == 200:
continue
except requests.ConnectionError:
raise MoulinetteError(errno.ENETUNREACH,
m18n.n('no_internet_connection'))
domain = _domain
key = path
break
if not domain:
raise MoulinetteError(errno.EINVAL,
m18n.n('dyndns_no_domain_registered'))
if key is None:
keys = glob.glob(
'/etc/yunohost/dyndns/K{0}.+*.private'.format(domain))
if len(keys) > 0:
key = keys[0]
if not key:
raise MoulinetteError(errno.EIO,
m18n.n('dyndns_key_not_found'))
host = domain.split('.')[1:] host = domain.split('.')[1:]
host = '.'.join(host) host = '.'.join(host)
lines = [ lines = [
@ -209,11 +240,7 @@ def dyndns_update(dyn_host="dynhost.yunohost.org", domain=None, key=None,
for line in lines: for line in lines:
zone.write(line + '\n') zone.write(line + '\n')
if key is None: if os.system('/usr/bin/nsupdate -k %s /etc/yunohost/dyndns/zone' % key) == 0:
private_key_file = glob.glob('/etc/yunohost/dyndns/*.private')[0]
else:
private_key_file = key
if os.system('/usr/bin/nsupdate -k %s /etc/yunohost/dyndns/zone' % private_key_file) == 0:
logger.success(m18n.n('dyndns_ip_updated')) logger.success(m18n.n('dyndns_ip_updated'))
with open('/etc/yunohost/dyndns/old_ip', 'w') as f: with open('/etc/yunohost/dyndns/old_ip', 'w') as f:
f.write(ipv4) f.write(ipv4)

View file

@ -83,7 +83,7 @@ def firewall_allow(protocol, port, ipv4_only=False, ipv6_only=False,
firewall[i][p].append(port) firewall[i][p].append(port)
else: else:
ipv = "IPv%s" % i[3] ipv = "IPv%s" % i[3]
logger.warning(m18n.n('port_already_opened', port, ipv)) logger.warning(m18n.n('port_already_opened', port=port, ip_version=ipv))
# Add port forwarding with UPnP # Add port forwarding with UPnP
if not no_upnp and port not in firewall['uPnP'][p]: if not no_upnp and port not in firewall['uPnP'][p]:
firewall['uPnP'][p].append(port) firewall['uPnP'][p].append(port)
@ -140,7 +140,7 @@ def firewall_disallow(protocol, port, ipv4_only=False, ipv6_only=False,
firewall[i][p].remove(port) firewall[i][p].remove(port)
else: else:
ipv = "IPv%s" % i[3] ipv = "IPv%s" % i[3]
logger.warning(m18n.n('port_already_closed', port, ipv)) logger.warning(m18n.n('port_already_closed', port=port, ip_version=ipv))
# Remove port forwarding with UPnP # Remove port forwarding with UPnP
if upnp and port in firewall['uPnP'][p]: if upnp and port in firewall['uPnP'][p]:
firewall['uPnP'][p].remove(port) firewall['uPnP'][p].remove(port)
@ -335,7 +335,7 @@ def firewall_upnp(action='status', no_refresh=False):
if action == 'status': if action == 'status':
no_refresh = True no_refresh = True
else: else:
raise MoulinetteError(errno.EINVAL, m18n.n('action_invalid', action)) raise MoulinetteError(errno.EINVAL, m18n.n('action_invalid', action=action))
# Refresh port mapping using UPnP # Refresh port mapping using UPnP
if not no_refresh: if not no_refresh:

View file

@ -110,7 +110,7 @@ def hook_info(action, name):
}) })
if not hooks: if not hooks:
raise MoulinetteError(errno.EINVAL, m18n.n('hook_name_unknown', name)) raise MoulinetteError(errno.EINVAL, m18n.n('hook_name_unknown', name=name))
return { return {
'action': action, 'action': action,
'name': name, 'name': name,
@ -275,7 +275,8 @@ def hook_callback(action, hooks=[], args=None):
return result return result
def hook_exec(path, args=None, raise_on_error=False, no_trace=False, env=None): def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
chdir=None, env=None):
""" """
Execute hook from a file with arguments Execute hook from a file with arguments
@ -284,6 +285,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, env=None):
args -- A list of arguments to pass to the script args -- A list of arguments to pass to the script
raise_on_error -- Raise if the script returns a non-zero exit code raise_on_error -- Raise if the script returns a non-zero exit code
no_trace -- Do not print each command that will be executed no_trace -- Do not print each command that will be executed
chdir -- The directory from where the script will be executed
env -- Dictionnary of environment variables to export env -- Dictionnary of environment variables to export
""" """
@ -297,50 +299,54 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False, env=None):
raise MoulinetteError(errno.EIO, m18n.g('file_not_exist')) raise MoulinetteError(errno.EIO, m18n.g('file_not_exist'))
# Construct command variables # Construct command variables
cmd_fdir, cmd_fname = os.path.split(path)
cmd_fname = './{0}'.format(cmd_fname)
cmd_args = '' cmd_args = ''
if args and isinstance(args, list): if args and isinstance(args, list):
# Concatenate arguments and escape them with double quotes to prevent # Concatenate arguments and escape them with double quotes to prevent
# bash related issue if an argument is empty and is not the last # bash related issue if an argument is empty and is not the last
cmd_args = '"{:s}"'.format('" "'.join(str(s) for s in args)) cmd_args = '"{:s}"'.format('" "'.join(str(s) for s in args))
if not chdir:
# use the script directory as current one
chdir, cmd_script = os.path.split(path)
cmd_script = './{0}'.format(cmd_script)
else:
cmd_script = path
envcli = '' envcli = ''
if env is not None: if env is not None:
envcli = ' '.join([ '{key}="{val}"'.format(key=key, val=val) for key,val in env.items()]) envcli = ' '.join([ '{key}="{val}"'.format(key=key, val=val) for key,val in env.items()])
# Construct command to execute # Construct command to execute
command = ['sudo', '-u', 'admin', '-H', 'sh', '-c'] command = ['sudo', '-n', '-u', 'admin', '-H', 'sh', '-c']
if no_trace: if no_trace:
cmd = 'cd "{0:s}" && {1:s} /bin/bash "{2:s}" {3:s}' cmd = '{envcli} /bin/bash "{script}" {args}'
else: else:
# use xtrace on fd 7 which is redirected to stdout # use xtrace on fd 7 which is redirected to stdout
cmd = 'cd "{0:s}" && {1:s} BASH_XTRACEFD=7 /bin/bash -x "{2:s}" {3:s} 7>&1' cmd = '{envcli} BASH_XTRACEFD=7 /bin/bash -x "{script}" {args} 7>&1'
command.append(cmd.format(cmd_fdir, envcli, cmd_fname, cmd_args)) command.append(cmd.format(envcli=envcli, script=cmd_script, args=cmd_args))
if logger.isEnabledFor(log.DEBUG): if logger.isEnabledFor(log.DEBUG):
logger.info(m18n.n('executing_command', command=' '.join(command))) logger.info(m18n.n('executing_command', command=' '.join(command)))
else: else:
logger.info(m18n.n('executing_script', script='{0}/{1}'.format( logger.info(m18n.n('executing_script', script=path))
cmd_fdir, cmd_fname)))
# Define output callbacks and call command # Define output callbacks and call command
callbacks = ( callbacks = (
lambda l: logger.info(l.rstrip()), lambda l: logger.info(l.rstrip()),
lambda l: logger.warning(l.rstrip()), lambda l: logger.warning(l.rstrip()),
) )
returncode = call_async_output(command, callbacks, shell=False) returncode = call_async_output(
command, callbacks, shell=False, cwd=chdir
)
# Check and return process' return code # Check and return process' return code
if returncode is None: if returncode is None:
if raise_on_error: if raise_on_error:
raise MoulinetteError(m18n.n('hook_exec_not_terminated')) raise MoulinetteError(m18n.n('hook_exec_not_terminated', path=path))
else: else:
logger.error(m18n.n('hook_exec_not_terminated')) logger.error(m18n.n('hook_exec_not_terminated', path=path))
return 1 return 1
elif raise_on_error and returncode != 0: elif raise_on_error and returncode != 0:
raise MoulinetteError(m18n.n('hook_exec_failed')) raise MoulinetteError(m18n.n('hook_exec_failed', path=path))
return returncode return returncode

View file

@ -139,7 +139,7 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False):
for dname in devices_names: for dname in devices_names:
_set(dname, 'not-available') _set(dname, 'not-available')
else: else:
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', u)) raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u))
if result_dname is not None: if result_dname is not None:
return result[result_dname] return result[result_dname]
@ -237,7 +237,7 @@ def monitor_network(units=None, human_readable=False):
'gateway': gateway, 'gateway': gateway,
} }
else: else:
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', u)) raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u))
if len(units) == 1: if len(units) == 1:
return result[units[0]] return result[units[0]]
@ -287,7 +287,7 @@ def monitor_system(units=None, human_readable=False):
elif u == 'infos': elif u == 'infos':
result[u] = json.loads(glances.getSystem()) result[u] = json.loads(glances.getSystem())
else: else:
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', u)) raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u))
if len(units) == 1 and type(result[units[0]]) is not str: if len(units) == 1 and type(result[units[0]]) is not str:
return result[units[0]] return result[units[0]]

View file

@ -75,9 +75,9 @@ def service_add(name, status=None, log=None, runlevel=None):
try: try:
_save_services(services) _save_services(services)
except: except:
raise MoulinetteError(errno.EIO, m18n.n('service_add_failed', name)) raise MoulinetteError(errno.EIO, m18n.n('service_add_failed', service=name))
logger.success(m18n.n('service_added')) logger.success(m18n.n('service_added', service=name))
def service_remove(name): def service_remove(name):
@ -93,14 +93,14 @@ def service_remove(name):
try: try:
del services[name] del services[name]
except KeyError: except KeyError:
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', name)) raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=name))
try: try:
_save_services(services) _save_services(services)
except: except:
raise MoulinetteError(errno.EIO, m18n.n('service_remove_failed', name)) raise MoulinetteError(errno.EIO, m18n.n('service_remove_failed', service=name))
logger.success(m18n.n('service_removed')) logger.success(m18n.n('service_removed', service=name))
def service_start(names): def service_start(names):
@ -115,12 +115,12 @@ def service_start(names):
names = [names] names = [names]
for name in names: for name in names:
if _run_service_command('start', name): if _run_service_command('start', name):
logger.success(m18n.n('service_started', name)) logger.success(m18n.n('service_started', service=name))
else: else:
if service_status(name)['status'] != 'running': if service_status(name)['status'] != 'running':
raise MoulinetteError(errno.EPERM, raise MoulinetteError(errno.EPERM,
m18n.n('service_start_failed', name)) m18n.n('service_start_failed', service=name))
logger.info(m18n.n('service_already_started', name)) logger.info(m18n.n('service_already_started', service=name))
def service_stop(names): def service_stop(names):
@ -135,12 +135,12 @@ def service_stop(names):
names = [names] names = [names]
for name in names: for name in names:
if _run_service_command('stop', name): if _run_service_command('stop', name):
logger.success(m18n.n('service_stopped', name)) logger.success(m18n.n('service_stopped', service=name))
else: else:
if service_status(name)['status'] != 'inactive': if service_status(name)['status'] != 'inactive':
raise MoulinetteError(errno.EPERM, raise MoulinetteError(errno.EPERM,
m18n.n('service_stop_failed', name)) m18n.n('service_stop_failed', service=name))
logger.info(m18n.n('service_already_stopped', name)) logger.info(m18n.n('service_already_stopped', service=name))
def service_enable(names): def service_enable(names):
@ -155,10 +155,10 @@ def service_enable(names):
names = [names] names = [names]
for name in names: for name in names:
if _run_service_command('enable', name): if _run_service_command('enable', name):
logger.success(m18n.n('service_enabled', name)) logger.success(m18n.n('service_enabled', service=name))
else: else:
raise MoulinetteError(errno.EPERM, raise MoulinetteError(errno.EPERM,
m18n.n('service_enable_failed', name)) m18n.n('service_enable_failed', service=name))
def service_disable(names): def service_disable(names):
@ -173,10 +173,10 @@ def service_disable(names):
names = [names] names = [names]
for name in names: for name in names:
if _run_service_command('disable', name): if _run_service_command('disable', name):
logger.success(m18n.n('service_disabled', name)) logger.success(m18n.n('service_disabled', service=name))
else: else:
raise MoulinetteError(errno.EPERM, raise MoulinetteError(errno.EPERM,
m18n.n('service_disable_failed', name)) m18n.n('service_disable_failed', service=name))
def service_status(names=[]): def service_status(names=[]):
@ -200,7 +200,7 @@ def service_status(names=[]):
for name in names: for name in names:
if check_names and name not in services.keys(): if check_names and name not in services.keys():
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('service_unknown', name)) m18n.n('service_unknown', service=name))
status = None status = None
if 'status' not in services[name] or \ if 'status' not in services[name] or \
@ -221,7 +221,7 @@ def service_status(names=[]):
shell=True) shell=True)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
if 'usage:' in e.output.lower(): if 'usage:' in e.output.lower():
logger.warning(m18n.n('service_status_failed', name)) logger.warning(m18n.n('service_status_failed', service=name))
else: else:
result[name]['status'] = 'inactive' result[name]['status'] = 'inactive'
else: else:
@ -253,7 +253,7 @@ def service_log(name, number=50):
services = _get_services() services = _get_services()
if name not in services.keys(): if name not in services.keys():
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', name)) raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=name))
if 'log' in services[name]: if 'log' in services[name]:
log_list = services[name]['log'] log_list = services[name]['log']
@ -268,7 +268,7 @@ def service_log(name, number=50):
else: else:
result[log_path] = _tail(log_path, int(number)) result[log_path] = _tail(log_path, int(number))
else: else:
raise MoulinetteError(errno.EPERM, m18n.n('service_no_log', name)) raise MoulinetteError(errno.EPERM, m18n.n('service_no_log', service=name))
return result return result
@ -289,9 +289,10 @@ def service_regenconf(service=None, force=False):
if service is not None: if service is not None:
hook_callback('conf_regen', [service], args=[force]) hook_callback('conf_regen', [service], args=[force])
logger.success(m18n.n('service_configured', service=service))
else: else:
hook_callback('conf_regen', args=[force]) hook_callback('conf_regen', args=[force])
logger.success(m18n.n('services_configured')) logger.success(m18n.n('service_configured_all'))
def _run_service_command(action, service): def _run_service_command(action, service):
@ -304,8 +305,7 @@ def _run_service_command(action, service):
""" """
if service not in _get_services().keys(): if service not in _get_services().keys():
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=service))
service))
cmd = None cmd = None
if action in ['start', 'stop', 'restart', 'reload']: if action in ['start', 'stop', 'restart', 'reload']:
@ -320,7 +320,7 @@ def _run_service_command(action, service):
ret = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT) ret = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
# TODO: Log output? # TODO: Log output?
logger.warning(m18n.n('service_cmd_exec_failed', ' '.join(e.cmd))) logger.warning(m18n.n('service_cmd_exec_failed', command=' '.join(e.cmd)))
return False return False
return True return True

View file

@ -380,7 +380,7 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False):
# ... and set a hourly cron up to upgrade critical packages # ... and set a hourly cron up to upgrade critical packages
if critical_upgrades: if critical_upgrades:
logger.info(m18n.n('packages_upgrade_critical_later', logger.info(m18n.n('packages_upgrade_critical_later',
', '.join(critical_upgrades))) packages=', '.join(critical_upgrades)))
with open('/etc/cron.d/yunohost-upgrade', 'w+') as f: with open('/etc/cron.d/yunohost-upgrade', 'w+') as f:
f.write('00 * * * * root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin apt-get install %s -y && rm -f /etc/cron.d/yunohost-upgrade\n' % ' '.join(critical_upgrades)) f.write('00 * * * * root PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin apt-get install %s -y && rm -f /etc/cron.d/yunohost-upgrade\n' % ' '.join(critical_upgrades))

View file

@ -128,7 +128,7 @@ def user_create(auth, username, firstname, lastname, mail, password,
if mail[mail.find('@')+1:] not in domain_list(auth)['domains']: if mail[mail.find('@')+1:] not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('mail_domain_unknown', m18n.n('mail_domain_unknown',
mail[mail.find('@')+1:])) domain=mail[mail.find('@')+1:]))
# Get random UID/GID # Get random UID/GID
uid_check = gid_check = 0 uid_check = gid_check = 0
@ -275,7 +275,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None,
# Populate user informations # Populate user informations
result = auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch) result = auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch)
if not result: if not result:
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown')) raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
user = result[0] user = result[0]
# Get modifications from arguments # Get modifications from arguments
@ -301,7 +301,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None,
if mail[mail.find('@')+1:] not in domains: if mail[mail.find('@')+1:] not in domains:
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('mail_domain_unknown', m18n.n('mail_domain_unknown',
mail[mail.find('@')+1:])) domain=mail[mail.find('@')+1:]))
del user['mail'][0] del user['mail'][0]
new_attr_dict['mail'] = [mail] + user['mail'] new_attr_dict['mail'] = [mail] + user['mail']
@ -313,7 +313,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None,
if mail[mail.find('@')+1:] not in domains: if mail[mail.find('@')+1:] not in domains:
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('mail_domain_unknown', m18n.n('mail_domain_unknown',
mail[mail.find('@')+1:])) domain=mail[mail.find('@')+1:]))
user['mail'].append(mail) user['mail'].append(mail)
new_attr_dict['mail'] = user['mail'] new_attr_dict['mail'] = user['mail']
@ -325,7 +325,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None,
user['mail'].remove(mail) user['mail'].remove(mail)
else: else:
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('mail_alias_remove_failed', mail)) m18n.n('mail_alias_remove_failed', mail=mail))
new_attr_dict['mail'] = user['mail'] new_attr_dict['mail'] = user['mail']
if add_mailforward: if add_mailforward:
@ -345,7 +345,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None,
user['maildrop'].remove(mail) user['maildrop'].remove(mail)
else: else:
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('mail_forward_remove_failed', mail)) m18n.n('mail_forward_remove_failed', mail=mail))
new_attr_dict['maildrop'] = user['maildrop'] new_attr_dict['maildrop'] = user['maildrop']
if mailbox_quota is not None: if mailbox_quota is not None:
@ -381,7 +381,7 @@ def user_info(auth, username):
if result: if result:
user = result[0] user = result[0]
else: else:
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown')) raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
result_dict = { result_dict = {
'username': user['uid'][0], 'username': user['uid'][0],