mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Manual merge with unstable
This commit is contained in:
commit
3ef177ea3b
22 changed files with 387 additions and 1430 deletions
28
bin/yunohost
28
bin/yunohost
|
@ -1,8 +1,13 @@
|
|||
#!/usr/bin/env python
|
||||
#! /usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
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
|
||||
IN_DEVEL = False
|
||||
|
@ -34,17 +39,11 @@ if IN_DEVEL:
|
|||
|
||||
def _die(message, title='Error:'):
|
||||
"""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))
|
||||
sys.exit(1)
|
||||
|
||||
def _parse_cli_args():
|
||||
"""Parse additional arguments for the cli"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--no-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):
|
||||
"""Configure logging and initialize the moulinette"""
|
||||
from moulinette import init
|
||||
|
||||
# Define loggers handlers
|
||||
handlers = set(LOGGERS_HANDLERS)
|
||||
if quiet and 'tty' in handlers:
|
||||
|
@ -100,7 +97,7 @@ def _init_moulinette(debug=False, verbose=False, quiet=False):
|
|||
handlers.append('tty')
|
||||
|
||||
root_handlers = set(handlers)
|
||||
if not debug:
|
||||
if not debug and 'tty' in root_handlers:
|
||||
root_handlers.remove('tty')
|
||||
|
||||
# Define loggers level
|
||||
|
@ -167,11 +164,10 @@ def _init_moulinette(debug=False, verbose=False, quiet=False):
|
|||
_die(str(e))
|
||||
|
||||
# Initialize moulinette
|
||||
init(logging_config=logging, _from_source=IN_DEVEL)
|
||||
moulinette.init(logging_config=logging, _from_source=IN_DEVEL)
|
||||
|
||||
def _retrieve_namespaces():
|
||||
"""Return the list of namespaces to load"""
|
||||
from moulinette.actionsmap import ActionsMap
|
||||
ret = ['yunohost']
|
||||
for n in ActionsMap.get_namespaces():
|
||||
# Append YunoHost modules
|
||||
|
@ -190,8 +186,6 @@ if __name__ == '__main__':
|
|||
if not os.path.isfile('/etc/yunohost/installed') and \
|
||||
(len(args) < 2 or (args[0] +' '+ args[1] != 'tools postinstall' and \
|
||||
args[0] +' '+ args[1] != 'backup restore')):
|
||||
from moulinette.interfaces.cli import get_locale
|
||||
|
||||
# Init i18n
|
||||
m18n.load_namespace('yunohost')
|
||||
m18n.set_locale(get_locale())
|
||||
|
@ -200,8 +194,8 @@ if __name__ == '__main__':
|
|||
_die(m18n.n('yunohost_not_installed'), m18n.g('error'))
|
||||
|
||||
# Execute the action
|
||||
from moulinette import cli
|
||||
ret = cli(_retrieve_namespaces(), args,
|
||||
ret = moulinette.cli(
|
||||
_retrieve_namespaces(), args,
|
||||
use_cache=opts.use_cache, output_as=opts.output_as,
|
||||
password=opts.password, parser_kwargs={'top_parser': parser}
|
||||
)
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
#!/usr/bin/env python
|
||||
#! /usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
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
|
||||
IN_DEVEL = False
|
||||
|
@ -38,17 +43,11 @@ if IN_DEVEL:
|
|||
|
||||
def _die(message, title='Error:'):
|
||||
"""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))
|
||||
sys.exit(1)
|
||||
|
||||
def _parse_api_args():
|
||||
"""Parse main arguments for the api"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(add_help=False,
|
||||
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):
|
||||
"""Configure logging and initialize the moulinette"""
|
||||
from moulinette import init
|
||||
|
||||
# Define loggers handlers
|
||||
handlers = set(LOGGERS_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))
|
||||
|
||||
# Initialize moulinette
|
||||
init(logging_config=logging, _from_source=IN_DEVEL)
|
||||
moulinette.init(logging_config=logging, _from_source=IN_DEVEL)
|
||||
|
||||
def _retrieve_namespaces():
|
||||
"""Return the list of namespaces to load"""
|
||||
from moulinette.actionsmap import ActionsMap
|
||||
ret = ['yunohost']
|
||||
for n in ActionsMap.get_namespaces():
|
||||
# Append YunoHost modules
|
||||
|
@ -195,14 +191,12 @@ if __name__ == '__main__':
|
|||
_init_moulinette(opts.use_websocket, opts.debug, opts.verbose)
|
||||
|
||||
# Run the server
|
||||
from moulinette import api, MoulinetteError
|
||||
from yunohost.utils.packages import ynh_packages_version
|
||||
ret = api(_retrieve_namespaces(),
|
||||
host=opts.host, port=opts.port,
|
||||
routes={
|
||||
ret = moulinette.api(
|
||||
_retrieve_namespaces(),
|
||||
host=opts.host, port=opts.port, routes={
|
||||
('GET', '/installed'): is_installed,
|
||||
('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)
|
||||
|
|
|
@ -334,7 +334,7 @@ app:
|
|||
arguments:
|
||||
-u:
|
||||
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:
|
||||
full: --name
|
||||
help: Name of the list (default yunohost)
|
||||
|
@ -377,6 +377,14 @@ app:
|
|||
full: --raw
|
||||
help: Return the full app_dict
|
||||
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()
|
||||
info:
|
||||
|
@ -428,7 +436,7 @@ app:
|
|||
help: Custom name for the app
|
||||
-a:
|
||||
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
|
||||
remove:
|
||||
|
@ -639,6 +647,8 @@ backup:
|
|||
action_help: Restore from a local backup archive
|
||||
api: POST /backup/restore/<name>
|
||||
configuration:
|
||||
authenticate: all
|
||||
authenticator: ldap-anonymous
|
||||
lock: false
|
||||
arguments:
|
||||
name:
|
||||
|
@ -969,7 +979,6 @@ service:
|
|||
action_help: >
|
||||
Check if the specific file has been modified and display differences.
|
||||
Stores the file hash in the services.yml file
|
||||
api: PUT /services/safecopy
|
||||
arguments:
|
||||
new_conf_file:
|
||||
help: Path to the desired conf file
|
||||
|
@ -990,7 +999,6 @@ service:
|
|||
action_help: >
|
||||
Check if the specific file has been modified before removing it.
|
||||
Backup the file in /home/yunohost.backup
|
||||
api: PUT /services/safecopy
|
||||
arguments:
|
||||
conf_file:
|
||||
help: Path to the targeted conf file
|
||||
|
@ -1159,10 +1167,10 @@ dyndns:
|
|||
arguments:
|
||||
--dyn-host:
|
||||
help: Dynette DNS server to inform
|
||||
default: "dynhost.yunohost.org"
|
||||
default: "dyndns.yunohost.org"
|
||||
-d:
|
||||
full: --domain
|
||||
help: Full domain to subscribe with
|
||||
help: Full domain to update
|
||||
extra:
|
||||
pattern: *pattern_domain
|
||||
-k:
|
||||
|
@ -1314,7 +1322,6 @@ hook:
|
|||
### hook_add()
|
||||
add:
|
||||
action_help: Store hook script to filesystem
|
||||
api: PUT /hook
|
||||
arguments:
|
||||
app:
|
||||
help: App to link with
|
||||
|
@ -1324,7 +1331,6 @@ hook:
|
|||
### hook_remove()
|
||||
remove:
|
||||
action_help: Remove hook scripts from filesystem
|
||||
api: DELETE /hook
|
||||
arguments:
|
||||
app:
|
||||
help: Scripts related to app will be removed
|
||||
|
@ -1362,7 +1368,7 @@ hook:
|
|||
### hook_callback()
|
||||
callback:
|
||||
action_help: Execute all scripts binded to an action
|
||||
api: GET /hooks
|
||||
api: POST /hooks/<action>
|
||||
arguments:
|
||||
action:
|
||||
help: Action name
|
||||
|
@ -1378,7 +1384,6 @@ hook:
|
|||
### hook_exec()
|
||||
exec:
|
||||
action_help: Execute hook from a file with arguments
|
||||
api: GET /hook
|
||||
arguments:
|
||||
path:
|
||||
help: Path of the script to execute
|
||||
|
@ -1392,3 +1397,6 @@ hook:
|
|||
full: --no-trace
|
||||
help: Do not print each command that will be executed
|
||||
action: store_true
|
||||
-d:
|
||||
full: --chdir
|
||||
help: The directory from where the script will be executed
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
# | arg: app - the application id
|
||||
# | arg: key - the setting to 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
|
||||
|
@ -14,7 +14,7 @@ ynh_app_setting_get() {
|
|||
# | arg: key - the setting name to set
|
||||
# | arg: value - the setting value to 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
|
||||
|
@ -23,5 +23,5 @@ ynh_app_setting_set() {
|
|||
# | arg: app - the application id
|
||||
# | arg: key - the setting to delete
|
||||
ynh_app_setting_delete() {
|
||||
sudo yunohost app setting -d "$1" "$2"
|
||||
sudo yunohost app setting -d "$1" "$2" --quiet
|
||||
}
|
||||
|
|
|
@ -14,8 +14,11 @@ function safe_copy () {
|
|||
|
||||
cd /usr/share/yunohost/templates/rspamd
|
||||
|
||||
# Copy Rspamd configuration
|
||||
safe_copy metrics.conf /etc/rspamd/metrics.conf
|
||||
# Create configuration directories
|
||||
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
|
||||
safe_copy rspamd.sieve /etc/dovecot/global_script/rspamd.sieve
|
||||
|
|
|
@ -17,11 +17,11 @@ function safe_copy () {
|
|||
cd /usr/share/yunohost/templates/dnsmasq
|
||||
|
||||
# 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'
|
||||
|
||||
# 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=''
|
||||
|
||||
sudo mkdir -p /etc/dnsmasq.d
|
||||
|
|
|
@ -29,11 +29,14 @@ server {
|
|||
return 302 https://$http_host/yunohost/admin;
|
||||
}
|
||||
|
||||
# Block crawlers bot
|
||||
location /yunohost {
|
||||
# Block crawlers bot
|
||||
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;
|
||||
|
|
File diff suppressed because it is too large
Load diff
8
data/templates/rspamd/metrics.conf.local
Normal file
8
data/templates/rspamd/metrics.conf.local
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Metrics settings
|
||||
# This define overridden options.
|
||||
|
||||
actions {
|
||||
reject = 21;
|
||||
add_header = 8;
|
||||
greylist = 4;
|
||||
}
|
69
debian/changelog
vendored
69
debian/changelog
vendored
|
@ -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
|
||||
|
||||
* [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
|
||||
|
||||
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
|
||||
|
||||
* [fix] Catch proper exception in backup_list (fix #65)
|
||||
|
|
77
debian/control
vendored
77
debian/control
vendored
|
@ -9,50 +9,41 @@ Homepage: https://yunohost.org/
|
|||
|
||||
Package: yunohost
|
||||
Architecture: all
|
||||
Depends: ${python:Depends}, ${misc:Depends},
|
||||
moulinette (>= 2.3.4),
|
||||
python-psutil,
|
||||
python-requests,
|
||||
glances,
|
||||
python-pip,
|
||||
python-miniupnpc | pyminiupnpc,
|
||||
dnsutils,
|
||||
bind9utils,
|
||||
python-apt,
|
||||
ca-certificates,
|
||||
python-dnspython,
|
||||
netcat-openbsd,
|
||||
iproute,
|
||||
unzip,
|
||||
git-core,
|
||||
curl,
|
||||
mariadb-server | mysql-server, php5-mysql | php5-mysqlnd,
|
||||
slapd, ldap-utils, sudo-ldap, libnss-ldapd,
|
||||
postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail,
|
||||
dovecot-ldap, dovecot-lmtpd, dovecot-managesieved,
|
||||
dovecot-antispam, fail2ban,
|
||||
nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl,
|
||||
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
|
||||
Depends: ${python:Depends}, ${misc:Depends}
|
||||
, moulinette (>= 2.3.4)
|
||||
, python-psutil, python-requests, python-dnspython
|
||||
, python-apt, python-miniupnpc
|
||||
, glances
|
||||
, dnsutils, bind9utils, unzip, git, curl, cron
|
||||
, ca-certificates, netcat-openbsd, iproute
|
||||
, mariadb-server | mysql-server, php5-mysql | php5-mysqlnd
|
||||
, slapd, ldap-utils, sudo-ldap, libnss-ldapd
|
||||
, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail
|
||||
, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved
|
||||
, dovecot-antispam, fail2ban
|
||||
, nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl
|
||||
, dnsmasq, openssl, avahi-daemon
|
||||
, ssowat, metronome
|
||||
, rspamd (>= 1.2.0), rmilter (>=1.7.0), redis-server, opendkim-tools
|
||||
Recommends: yunohost-admin
|
||||
, openssh-server, ntp, inetutils-ping | iputils-ping
|
||||
, bash-completion, rsyslog
|
||||
, php5-gd, php5-curl, php-gettext, php5-mcrypt
|
||||
, python-pip
|
||||
, unattended-upgrades
|
||||
, libdbd-ldap-perl, libnet-dns-perl
|
||||
Suggests: htop, vim, rsync, acpi-support-base, udisks2
|
||||
Conflicts: iptables-persistent,
|
||||
moulinette-yunohost, yunohost-config,
|
||||
yunohost-config-others, yunohost-config-postfix,
|
||||
yunohost-config-dovecot, yunohost-config-slapd,
|
||||
yunohost-config-nginx, yunohost-config-amavis,
|
||||
yunohost-config-mysql, yunohost-predepends
|
||||
Replaces: moulinette-yunohost, yunohost-config,
|
||||
yunohost-config-others, yunohost-config-postfix,
|
||||
yunohost-config-dovecot, yunohost-config-slapd,
|
||||
yunohost-config-nginx, yunohost-config-amavis,
|
||||
yunohost-config-mysql, yunohost-predepends
|
||||
Conflicts: iptables-persistent
|
||||
, moulinette-yunohost, yunohost-config
|
||||
, yunohost-config-others, yunohost-config-postfix
|
||||
, yunohost-config-dovecot, yunohost-config-slapd
|
||||
, yunohost-config-nginx, yunohost-config-amavis
|
||||
, yunohost-config-mysql, yunohost-predepends
|
||||
Replaces: moulinette-yunohost, yunohost-config
|
||||
, yunohost-config-others, yunohost-config-postfix
|
||||
, yunohost-config-dovecot, yunohost-config-slapd
|
||||
, yunohost-config-nginx, yunohost-config-amavis
|
||||
, yunohost-config-mysql, yunohost-predepends
|
||||
Description: manageable and configured self-hosting server
|
||||
YunoHost aims to make self-hosting accessible to everyone. It configures
|
||||
an email, Web and IM server alongside a LDAP base. It also provides
|
||||
|
|
3
debian/rules
vendored
3
debian/rules
vendored
|
@ -8,7 +8,8 @@
|
|||
dh ${@} --with=python2,systemd
|
||||
|
||||
override_dh_installinit:
|
||||
dh_installinit --noscripts
|
||||
dh_installinit -pyunohost --name=yunohost-api --noscripts
|
||||
dh_installinit -pyunohost --name=yunohost-firewall --noscripts
|
||||
|
||||
override_dh_systemd_enable:
|
||||
dh_systemd_enable --name=yunohost-api
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"installation_complete" : "Installation complete",
|
||||
"installation_failed" : "Installation failed",
|
||||
"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}'",
|
||||
|
||||
"license_undefined" : "undefined",
|
||||
|
@ -27,6 +27,7 @@
|
|||
"app_upgrade_failed" : "Unable to upgrade {app:s}",
|
||||
"app_id_invalid" : "Invalid app id",
|
||||
"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_location_already_used" : "An app is already installed 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",
|
||||
|
||||
"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_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_ip_update_failed" : "Unable to update IP address on DynDNS",
|
||||
"dyndns_ip_updated" : "IP address successfully updated on DynDNS",
|
||||
|
@ -79,8 +82,8 @@
|
|||
|
||||
"port_available" : "Port {port:d} is available",
|
||||
"port_unavailable" : "Port {port:d} is not available",
|
||||
"port_already_opened" : "Port {} is already opened for {:s} connections",
|
||||
"port_already_closed" : "Port {} is already closed for {:s} connections",
|
||||
"port_already_opened" : "Port {port:d} is already opened for {ip_version: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.",
|
||||
"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",
|
||||
|
@ -92,12 +95,12 @@
|
|||
"firewall_reloaded" : "Firewall successfully reloaded",
|
||||
|
||||
"hook_list_by_invalid" : "Invalid property to list hook by",
|
||||
"hook_name_unknown" : "Unknown hook name '{:s}'",
|
||||
"hook_exec_failed" : "Script execution failed",
|
||||
"hook_exec_not_terminated" : "Script execution hasn’t terminated",
|
||||
"hook_name_unknown" : "Unknown hook name '{name:s}'",
|
||||
"hook_exec_failed" : "Script execution failed: {path:s}",
|
||||
"hook_exec_not_terminated" : "Script execution hasn’t terminated: {path:s}",
|
||||
|
||||
"mountpoint_unknown" : "Unknown mountpoint",
|
||||
"unit_unknown" : "Unknown unit '{:s}'",
|
||||
"unit_unknown" : "Unknown unit '{unit:s}'",
|
||||
"monitor_period_invalid" : "Invalid time period",
|
||||
"monitor_stats_no_update" : "No monitoring statistics to update",
|
||||
"monitor_stats_file_not_found" : "Statistics file not found",
|
||||
|
@ -107,25 +110,26 @@
|
|||
"monitor_not_enabled" : "Server monitoring is not enabled",
|
||||
"monitor_glances_con_failed" : "Unable to connect to Glances server",
|
||||
|
||||
"service_unknown" : "Unknown service '{:s}'",
|
||||
"service_add_failed" : "Unable to add service '{:s}'",
|
||||
"service_added" : "Service successfully added",
|
||||
"service_remove_failed" : "Unable to remove service '{:s}'",
|
||||
"service_removed" : "Service successfully removed",
|
||||
"service_start_failed" : "Unable to start service '{:s}'",
|
||||
"service_already_started" : "Service '{:s}' is already started",
|
||||
"service_started" : "Service '{:s}' successfully started",
|
||||
"service_stop_failed" : "Unable to stop service '{:s}'",
|
||||
"service_already_stopped" : "Service '{:s}' is already stopped",
|
||||
"service_stopped" : "Service '{:s}' successfully stopped",
|
||||
"service_enable_failed" : "Unable to enable service '{:s}'",
|
||||
"service_enabled" : "Service '{:s}' successfully enabled",
|
||||
"service_disable_failed" : "Unable to disable service '{:s}'",
|
||||
"service_disabled" : "Service '{:s}' successfully disabled",
|
||||
"service_status_failed" : "Unable to determine status of service '{:s}'",
|
||||
"service_no_log" : "No log to display for service '{:s}'",
|
||||
"service_cmd_exec_failed" : "Unable to execute command '{:s}'",
|
||||
"services_configured": "Configuration successfully generated",
|
||||
"service_unknown" : "Unknown service '{service:s}'",
|
||||
"service_add_failed" : "Unable to add service '{service:s}'",
|
||||
"service_added" : "Service successfully added: '{service:s}'",
|
||||
"service_remove_failed" : "Unable to remove service '{service:s}'",
|
||||
"service_removed" : "Service successfully removed: '{service:s}'",
|
||||
"service_start_failed" : "Unable to start service '{service:s}'",
|
||||
"service_already_started" : "Service '{service:s}' is already started",
|
||||
"service_started" : "Service '{service:s}' successfully started",
|
||||
"service_stop_failed" : "Unable to stop service '{service:s}'",
|
||||
"service_already_stopped" : "Service '{service:s}' is already stopped",
|
||||
"service_stopped" : "Service '{service:s}' successfully stopped",
|
||||
"service_enable_failed" : "Unable to enable service '{service:s}'",
|
||||
"service_enabled" : "Service '{service:s}' successfully enabled",
|
||||
"service_disable_failed" : "Unable to disable service '{service:s}'",
|
||||
"service_disabled" : "Service '{service:s}' successfully disabled",
|
||||
"service_status_failed" : "Unable to determine status of service '{service:s}'",
|
||||
"service_no_log" : "No log to display for service '{service:s}'",
|
||||
"service_cmd_exec_failed" : "Unable to execute command '{command:s}'",
|
||||
"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).",
|
||||
"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}",
|
||||
|
@ -148,14 +152,14 @@
|
|||
"updating_apt_cache" : "Updating the lists of available packages...",
|
||||
"update_cache_failed" : "Unable to update APT cache",
|
||||
"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...",
|
||||
"packages_upgrade_failed" : "Unable to upgrade all packages",
|
||||
"system_upgraded" : "System successfully upgraded",
|
||||
|
||||
"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_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_hook_unknown" : "Backup hook '{hook:s}' unknown",
|
||||
"backup_running_hooks" : "Running backup hooks...",
|
||||
|
@ -190,10 +194,10 @@
|
|||
"backup_deleted" : "Backup successfully deleted",
|
||||
|
||||
"field_invalid" : "Invalid field '{:s}'",
|
||||
"mail_domain_unknown" : "Unknown mail address domain '{:s}'",
|
||||
"mail_alias_remove_failed" : "Unable to remove mail alias '{:s}'",
|
||||
"mail_forward_remove_failed" : "Unable to remove mail forward '{:s}'",
|
||||
"user_unknown" : "Unknown user",
|
||||
"mail_domain_unknown" : "Unknown mail address domain '{domain:s}'",
|
||||
"mail_alias_remove_failed" : "Unable to remove mail alias '{mail:s}'",
|
||||
"mail_forward_remove_failed" : "Unable to remove mail forward '{mail:s}'",
|
||||
"user_unknown" : "Unknown user: {user:s}",
|
||||
"system_username_exists" : "Username already exists in the system users",
|
||||
"user_creation_failed" : "Unable to create user",
|
||||
"user_created" : "User successfully created",
|
||||
|
|
|
@ -85,7 +85,7 @@ def app_fetchlist(url=None, name=None):
|
|||
|
||||
Keyword argument:
|
||||
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
|
||||
|
@ -93,7 +93,7 @@ def app_fetchlist(url=None, name=None):
|
|||
except OSError: os.makedirs(repo_path)
|
||||
|
||||
if url is None:
|
||||
url = 'https://yunohost.org/official.json'
|
||||
url = 'https://app.yunohost.org/official.json'
|
||||
name = 'yunohost'
|
||||
else:
|
||||
if name is None:
|
||||
|
@ -131,7 +131,7 @@ def app_removelist(name):
|
|||
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
|
||||
|
||||
|
@ -140,12 +140,15 @@ def app_list(offset=None, limit=None, filter=None, raw=False):
|
|||
offset -- Starting number for app fetching
|
||||
limit -- Maximum number of app fetched
|
||||
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)
|
||||
else: offset = 0
|
||||
if limit: limit = int(limit)
|
||||
else: limit = 1000
|
||||
installed = with_backup or installed
|
||||
|
||||
app_dict = {}
|
||||
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():
|
||||
if i < limit:
|
||||
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:
|
||||
app_info_dict['installed'] = installed
|
||||
if installed:
|
||||
app_info_dict['installed'] = app_installed
|
||||
if app_installed:
|
||||
app_info_dict['status'] = _get_app_status(app_id)
|
||||
list_dict[app_id] = app_info_dict
|
||||
else:
|
||||
label = None
|
||||
if installed:
|
||||
if app_installed:
|
||||
app_info_dict_raw = app_info(app=app_id, raw=True)
|
||||
label = app_info_dict_raw['settings']['label']
|
||||
list_dict.append({
|
||||
|
@ -209,7 +223,7 @@ def app_list(offset=None, limit=None, filter=None, raw=False):
|
|||
# FIXME: Temporarly allow undefined license
|
||||
'license': app_info_dict['manifest'].get('license',
|
||||
m18n.n('license_undefined')),
|
||||
'installed': installed
|
||||
'installed': app_installed
|
||||
})
|
||||
i += 1
|
||||
else:
|
||||
|
@ -515,8 +529,40 @@ def app_install(auth, app, label=None, args=None):
|
|||
# Move scripts and manifest to the right place
|
||||
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))
|
||||
|
||||
# Execute the app install script
|
||||
install_retcode = 1
|
||||
try:
|
||||
if hook_exec(app_tmp_folder + '/scripts/install', args=args_list, env=env_dict) == 0:
|
||||
install_retcode = hook_exec(
|
||||
os.path.join(app_tmp_folder, 'scripts/install'), args_list, env=env_dict)
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
install_retcode = -1
|
||||
except:
|
||||
logger.exception(m18n.n('unexpected_error'))
|
||||
finally:
|
||||
if install_retcode != 0:
|
||||
# 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)
|
||||
|
||||
# Execute remove script
|
||||
remove_retcode = hook_exec(
|
||||
os.path.join(app_tmp_folder, 'scripts/remove'), args=[app_instance_name], env=env_dict_remove)
|
||||
if remove_retcode != 0:
|
||||
logger.warning(m18n.n('app_not_properly_removed', app=app_instance_name))
|
||||
|
||||
# Clean tmp folders
|
||||
hook_remove(app_instance_name)
|
||||
shutil.rmtree(app_setting_path)
|
||||
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)
|
||||
|
@ -526,26 +572,10 @@ def app_install(auth, app, label=None, args=None):
|
|||
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:
|
||||
# Execute remove script and clean folders
|
||||
hook_remove(app_instance_name)
|
||||
shutil.rmtree(app_setting_path)
|
||||
shutil.rmtree(app_tmp_folder)
|
||||
|
||||
# Reraise proper exception
|
||||
try:
|
||||
raise
|
||||
except MoulinetteError:
|
||||
raise
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
raise MoulinetteError(errno.EINTR, m18n.g('operation_interrupted'))
|
||||
except Exception as e:
|
||||
logger.debug('app installation failed', exc_info=1)
|
||||
raise MoulinetteError(errno.EIO, m18n.n('unexpected_error'))
|
||||
app_ssowatconf(auth)
|
||||
|
||||
logger.success(m18n.n('installation_complete'))
|
||||
|
||||
|
||||
def app_remove(auth, app):
|
||||
|
@ -624,10 +654,7 @@ def app_addaccess(auth, apps, users=[]):
|
|||
try:
|
||||
user_info(auth, allowed_user)
|
||||
except MoulinetteError:
|
||||
# FIXME: Add username keyword in user_unknown
|
||||
logger.warning('{0}{1}'.format(
|
||||
m18n.g('colon', m18n.n('user_unknown')),
|
||||
allowed_user))
|
||||
logger.warning(m18n.n('user_unknown', user=allowed_user))
|
||||
continue
|
||||
allowed_users.add(allowed_user)
|
||||
|
||||
|
|
|
@ -39,6 +39,13 @@ from moulinette.core import MoulinetteError
|
|||
from moulinette.utils import filesystem
|
||||
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'
|
||||
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
|
||||
from yunohost.hook import hook_info, hook_callback, hook_exec
|
||||
|
||||
tmp_dir = None
|
||||
|
||||
# Validate what to backup
|
||||
|
@ -167,9 +172,6 @@ def backup_create(name=None, description=None, output_directory=None,
|
|||
|
||||
# Backup apps
|
||||
if not ignore_apps:
|
||||
from yunohost.app import app_info
|
||||
from yunohost.app import _parse_app_instance_name
|
||||
|
||||
# Filter applications to backup
|
||||
apps_list = set(os.listdir('/etc/yunohost/apps'))
|
||||
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)
|
||||
|
||||
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:
|
||||
logger.exception(m18n.n('backup_app_failed', app=app_instance_name))
|
||||
# Cleaning app backup directory
|
||||
|
@ -288,21 +291,20 @@ def backup_create(name=None, description=None, output_directory=None,
|
|||
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
|
||||
|
||||
Keyword argument:
|
||||
name -- Name of the local backup archive
|
||||
hooks -- List of restoration hooks names to execute
|
||||
ignore_hooks -- Do not execute backup hooks
|
||||
apps -- List of application names to restore
|
||||
ignore_apps -- Do not restore apps
|
||||
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
|
||||
if ignore_hooks and ignore_apps:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
|
@ -380,8 +382,6 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals
|
|||
_clean_tmp_dir()
|
||||
raise MoulinetteError(errno.EEXIST, m18n.n('restore_failed'))
|
||||
else:
|
||||
from yunohost.tools import tools_postinstall
|
||||
|
||||
# Retrieve the domain from the backup
|
||||
try:
|
||||
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
|
||||
if not ignore_apps:
|
||||
from yunohost.app import _is_installed
|
||||
from yunohost.app import _parse_app_instance_name
|
||||
|
||||
# Filter applications to restore
|
||||
apps_list = set(info['apps'].keys())
|
||||
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:
|
||||
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
|
||||
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)
|
||||
|
||||
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:
|
||||
logger.exception(m18n.n('restore_app_failed', app=app_instance_name))
|
||||
# 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']:
|
||||
_clean_tmp_dir(1)
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('restore_nothings_done'))
|
||||
if result['apps']:
|
||||
app_ssowatconf(auth)
|
||||
|
||||
_clean_tmp_dir()
|
||||
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
|
||||
|
||||
"""
|
||||
from yunohost.monitor import binary_to_human
|
||||
|
||||
archive_file = '%s/%s.tar.gz' % (archives_path, name)
|
||||
if not os.path.isfile(archive_file):
|
||||
raise MoulinetteError(errno.EIO,
|
||||
|
@ -602,7 +600,6 @@ def backup_delete(name):
|
|||
name -- Name of the local backup archive
|
||||
|
||||
"""
|
||||
from yunohost.hook import hook_callback
|
||||
hook_callback('pre_backup_delete', args=[name])
|
||||
|
||||
archive_file = '%s/%s.tar.gz' % (archives_path, name)
|
||||
|
|
|
@ -24,12 +24,12 @@
|
|||
Subscribe and Update DynDNS Hosts
|
||||
"""
|
||||
import os
|
||||
import requests
|
||||
import re
|
||||
import json
|
||||
import glob
|
||||
import base64
|
||||
import errno
|
||||
import requests
|
||||
import subprocess
|
||||
|
||||
from moulinette.core import MoulinetteError
|
||||
|
@ -62,6 +62,10 @@ class IPRouteLine(object):
|
|||
for k, v in self.m.groupdict().items():
|
||||
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):
|
||||
"""
|
||||
|
@ -107,30 +111,26 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None
|
|||
try: error = json.loads(r.text)['error']
|
||||
except: error = "Server error"
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('dyndns_registration_failed', error))
|
||||
m18n.n('dyndns_registration_failed', error=error))
|
||||
|
||||
logger.success(m18n.n('dyndns_registered'))
|
||||
|
||||
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):
|
||||
"""
|
||||
Update IP on DynDNS platform
|
||||
|
||||
Keyword argument:
|
||||
domain -- Full domain to subscribe with
|
||||
domain -- Full domain to update
|
||||
dyn_host -- Dynette DNS server to inform
|
||||
key -- Public DNS key
|
||||
ipv4 -- IP 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
|
||||
if ipv4 is None:
|
||||
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'
|
||||
|
||||
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 = '.'.join(host)
|
||||
lines = [
|
||||
|
@ -209,11 +240,7 @@ def dyndns_update(dyn_host="dynhost.yunohost.org", domain=None, key=None,
|
|||
for line in lines:
|
||||
zone.write(line + '\n')
|
||||
|
||||
if key is None:
|
||||
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:
|
||||
if os.system('/usr/bin/nsupdate -k %s /etc/yunohost/dyndns/zone' % key) == 0:
|
||||
logger.success(m18n.n('dyndns_ip_updated'))
|
||||
with open('/etc/yunohost/dyndns/old_ip', 'w') as f:
|
||||
f.write(ipv4)
|
||||
|
|
|
@ -83,7 +83,7 @@ def firewall_allow(protocol, port, ipv4_only=False, ipv6_only=False,
|
|||
firewall[i][p].append(port)
|
||||
else:
|
||||
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
|
||||
if not no_upnp and port not in firewall['uPnP'][p]:
|
||||
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)
|
||||
else:
|
||||
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
|
||||
if upnp and port in firewall['uPnP'][p]:
|
||||
firewall['uPnP'][p].remove(port)
|
||||
|
@ -335,7 +335,7 @@ def firewall_upnp(action='status', no_refresh=False):
|
|||
if action == 'status':
|
||||
no_refresh = True
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('action_invalid', action))
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('action_invalid', action=action))
|
||||
|
||||
# Refresh port mapping using UPnP
|
||||
if not no_refresh:
|
||||
|
|
|
@ -110,7 +110,7 @@ def hook_info(action, name):
|
|||
})
|
||||
|
||||
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 {
|
||||
'action': action,
|
||||
'name': name,
|
||||
|
@ -275,7 +275,8 @@ def hook_callback(action, hooks=[], args=None):
|
|||
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
|
||||
|
||||
|
@ -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
|
||||
raise_on_error -- Raise if the script returns a non-zero exit code
|
||||
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
|
||||
|
||||
"""
|
||||
|
@ -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'))
|
||||
|
||||
# Construct command variables
|
||||
cmd_fdir, cmd_fname = os.path.split(path)
|
||||
cmd_fname = './{0}'.format(cmd_fname)
|
||||
|
||||
cmd_args = ''
|
||||
if args and isinstance(args, list):
|
||||
# Concatenate arguments and escape them with double quotes to prevent
|
||||
# bash related issue if an argument is empty and is not the last
|
||||
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 = ''
|
||||
if env is not None:
|
||||
envcli = ' '.join([ '{key}="{val}"'.format(key=key, val=val) for key,val in env.items()])
|
||||
|
||||
# Construct command to execute
|
||||
command = ['sudo', '-u', 'admin', '-H', 'sh', '-c']
|
||||
command = ['sudo', '-n', '-u', 'admin', '-H', 'sh', '-c']
|
||||
if no_trace:
|
||||
cmd = 'cd "{0:s}" && {1:s} /bin/bash "{2:s}" {3:s}'
|
||||
cmd = '{envcli} /bin/bash "{script}" {args}'
|
||||
else:
|
||||
# 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'
|
||||
command.append(cmd.format(cmd_fdir, envcli, cmd_fname, cmd_args))
|
||||
cmd = '{envcli} BASH_XTRACEFD=7 /bin/bash -x "{script}" {args} 7>&1'
|
||||
command.append(cmd.format(envcli=envcli, script=cmd_script, args=cmd_args))
|
||||
|
||||
if logger.isEnabledFor(log.DEBUG):
|
||||
logger.info(m18n.n('executing_command', command=' '.join(command)))
|
||||
else:
|
||||
logger.info(m18n.n('executing_script', script='{0}/{1}'.format(
|
||||
cmd_fdir, cmd_fname)))
|
||||
logger.info(m18n.n('executing_script', script=path))
|
||||
|
||||
# Define output callbacks and call command
|
||||
callbacks = (
|
||||
lambda l: logger.info(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
|
||||
if returncode is None:
|
||||
if raise_on_error:
|
||||
raise MoulinetteError(m18n.n('hook_exec_not_terminated'))
|
||||
raise MoulinetteError(m18n.n('hook_exec_not_terminated', path=path))
|
||||
else:
|
||||
logger.error(m18n.n('hook_exec_not_terminated'))
|
||||
logger.error(m18n.n('hook_exec_not_terminated', path=path))
|
||||
return 1
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -139,7 +139,7 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False):
|
|||
for dname in devices_names:
|
||||
_set(dname, 'not-available')
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', u))
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u))
|
||||
|
||||
if result_dname is not None:
|
||||
return result[result_dname]
|
||||
|
@ -237,7 +237,7 @@ def monitor_network(units=None, human_readable=False):
|
|||
'gateway': gateway,
|
||||
}
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', u))
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u))
|
||||
|
||||
if len(units) == 1:
|
||||
return result[units[0]]
|
||||
|
@ -287,7 +287,7 @@ def monitor_system(units=None, human_readable=False):
|
|||
elif u == 'infos':
|
||||
result[u] = json.loads(glances.getSystem())
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', u))
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('unit_unknown', unit=u))
|
||||
|
||||
if len(units) == 1 and type(result[units[0]]) is not str:
|
||||
return result[units[0]]
|
||||
|
|
|
@ -75,9 +75,9 @@ def service_add(name, status=None, log=None, runlevel=None):
|
|||
try:
|
||||
_save_services(services)
|
||||
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):
|
||||
|
@ -93,14 +93,14 @@ def service_remove(name):
|
|||
try:
|
||||
del services[name]
|
||||
except KeyError:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', name))
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=name))
|
||||
|
||||
try:
|
||||
_save_services(services)
|
||||
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):
|
||||
|
@ -115,12 +115,12 @@ def service_start(names):
|
|||
names = [names]
|
||||
for name in names:
|
||||
if _run_service_command('start', name):
|
||||
logger.success(m18n.n('service_started', name))
|
||||
logger.success(m18n.n('service_started', service=name))
|
||||
else:
|
||||
if service_status(name)['status'] != 'running':
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_start_failed', name))
|
||||
logger.info(m18n.n('service_already_started', name))
|
||||
m18n.n('service_start_failed', service=name))
|
||||
logger.info(m18n.n('service_already_started', service=name))
|
||||
|
||||
|
||||
def service_stop(names):
|
||||
|
@ -135,12 +135,12 @@ def service_stop(names):
|
|||
names = [names]
|
||||
for name in names:
|
||||
if _run_service_command('stop', name):
|
||||
logger.success(m18n.n('service_stopped', name))
|
||||
logger.success(m18n.n('service_stopped', service=name))
|
||||
else:
|
||||
if service_status(name)['status'] != 'inactive':
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_stop_failed', name))
|
||||
logger.info(m18n.n('service_already_stopped', name))
|
||||
m18n.n('service_stop_failed', service=name))
|
||||
logger.info(m18n.n('service_already_stopped', service=name))
|
||||
|
||||
|
||||
def service_enable(names):
|
||||
|
@ -155,10 +155,10 @@ def service_enable(names):
|
|||
names = [names]
|
||||
for name in names:
|
||||
if _run_service_command('enable', name):
|
||||
logger.success(m18n.n('service_enabled', name))
|
||||
logger.success(m18n.n('service_enabled', service=name))
|
||||
else:
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_enable_failed', name))
|
||||
m18n.n('service_enable_failed', service=name))
|
||||
|
||||
|
||||
def service_disable(names):
|
||||
|
@ -173,10 +173,10 @@ def service_disable(names):
|
|||
names = [names]
|
||||
for name in names:
|
||||
if _run_service_command('disable', name):
|
||||
logger.success(m18n.n('service_disabled', name))
|
||||
logger.success(m18n.n('service_disabled', service=name))
|
||||
else:
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_disable_failed', name))
|
||||
m18n.n('service_disable_failed', service=name))
|
||||
|
||||
|
||||
def service_status(names=[]):
|
||||
|
@ -200,7 +200,7 @@ def service_status(names=[]):
|
|||
for name in names:
|
||||
if check_names and name not in services.keys():
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('service_unknown', name))
|
||||
m18n.n('service_unknown', service=name))
|
||||
|
||||
status = None
|
||||
if 'status' not in services[name] or \
|
||||
|
@ -221,7 +221,7 @@ def service_status(names=[]):
|
|||
shell=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
if 'usage:' in e.output.lower():
|
||||
logger.warning(m18n.n('service_status_failed', name))
|
||||
logger.warning(m18n.n('service_status_failed', service=name))
|
||||
else:
|
||||
result[name]['status'] = 'inactive'
|
||||
else:
|
||||
|
@ -253,7 +253,7 @@ def service_log(name, number=50):
|
|||
services = _get_services()
|
||||
|
||||
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]:
|
||||
log_list = services[name]['log']
|
||||
|
@ -268,7 +268,7 @@ def service_log(name, number=50):
|
|||
else:
|
||||
result[log_path] = _tail(log_path, int(number))
|
||||
else:
|
||||
raise MoulinetteError(errno.EPERM, m18n.n('service_no_log', name))
|
||||
raise MoulinetteError(errno.EPERM, m18n.n('service_no_log', service=name))
|
||||
|
||||
return result
|
||||
|
||||
|
@ -289,9 +289,10 @@ def service_regenconf(service=None, force=False):
|
|||
|
||||
if service is not None:
|
||||
hook_callback('conf_regen', [service], args=[force])
|
||||
logger.success(m18n.n('service_configured', service=service))
|
||||
else:
|
||||
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):
|
||||
|
@ -304,8 +305,7 @@ def _run_service_command(action, service):
|
|||
|
||||
"""
|
||||
if service not in _get_services().keys():
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown',
|
||||
service))
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=service))
|
||||
|
||||
cmd = None
|
||||
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)
|
||||
except subprocess.CalledProcessError as e:
|
||||
# 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 True
|
||||
|
||||
|
|
|
@ -380,7 +380,7 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False):
|
|||
# ... and set a hourly cron up to upgrade critical packages
|
||||
if critical_upgrades:
|
||||
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:
|
||||
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))
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ def user_create(auth, username, firstname, lastname, mail, password,
|
|||
if mail[mail.find('@')+1:] not in domain_list(auth)['domains']:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('mail_domain_unknown',
|
||||
mail[mail.find('@')+1:]))
|
||||
domain=mail[mail.find('@')+1:]))
|
||||
|
||||
# Get random UID/GID
|
||||
uid_check = gid_check = 0
|
||||
|
@ -275,7 +275,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None,
|
|||
# Populate user informations
|
||||
result = auth.search(base='ou=users,dc=yunohost,dc=org', filter='uid=' + username, attrs=attrs_to_fetch)
|
||||
if not result:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown'))
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
|
||||
user = result[0]
|
||||
|
||||
# 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:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('mail_domain_unknown',
|
||||
mail[mail.find('@')+1:]))
|
||||
domain=mail[mail.find('@')+1:]))
|
||||
del user['mail'][0]
|
||||
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:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('mail_domain_unknown',
|
||||
mail[mail.find('@')+1:]))
|
||||
domain=mail[mail.find('@')+1:]))
|
||||
user['mail'].append(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)
|
||||
else:
|
||||
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']
|
||||
|
||||
if add_mailforward:
|
||||
|
@ -345,7 +345,7 @@ def user_update(auth, username, firstname=None, lastname=None, mail=None,
|
|||
user['maildrop'].remove(mail)
|
||||
else:
|
||||
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']
|
||||
|
||||
if mailbox_quota is not None:
|
||||
|
@ -381,7 +381,7 @@ def user_info(auth, username):
|
|||
if result:
|
||||
user = result[0]
|
||||
else:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown'))
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('user_unknown', user=username))
|
||||
|
||||
result_dict = {
|
||||
'username': user['uid'][0],
|
||||
|
|
Loading…
Add table
Reference in a new issue