Merge remote-tracking branch 'upstream/unstable' into unstable

Conflicts:
	data/hooks/restore/05-conf_ldap
This commit is contained in:
zamentur 2016-02-08 22:52:28 +01:00
commit ea5ffb8498
98 changed files with 3862 additions and 2272 deletions

2
.gitignore vendored
View file

@ -31,4 +31,4 @@ pip-log.txt
.mr.developer.cfg .mr.developer.cfg
# moulinette lib # moulinette lib
lib/yunohost/locales src/yunohost/locales

2
README.md Normal file
View file

@ -0,0 +1,2 @@
Please report issues here (no registration needed):
https://dev.yunohost.org/projects/yunohost/issues

View file

@ -7,27 +7,28 @@ import os
# Either we are in a development environment or not # Either we are in a development environment or not
IN_DEVEL = False IN_DEVEL = False
# Either cache has to be used inside the moulinette or not
USE_CACHE = True
# Either the output has to be encoded as a JSON encoded string or not
PRINT_JSON = False
# Either the output has to printed for scripting usage or not
PRINT_PLAIN = False
# Level for which loggers will log # Level for which loggers will log
LOGGERS_LEVEL = 'INFO' LOGGERS_LEVEL = 'INFO'
TTY_LOG_LEVEL = 'SUCCESS'
# Handlers that will be used by loggers # Handlers that will be used by loggers
# - file: log to the file LOG_DIR/LOG_FILE # - file: log to the file LOG_DIR/LOG_FILE
# - console: log to stderr # - tty: log to current tty
LOGGERS_HANDLERS = ['file'] LOGGERS_HANDLERS = ['file', 'tty']
# Directory and file to be used by logging # Directory and file to be used by logging
LOG_DIR = '/var/log/yunohost' LOG_DIR = '/var/log/yunohost'
LOG_FILE = 'yunohost-cli.log' LOG_FILE = 'yunohost-cli.log'
# Check and load - as needed - development environment
if not __file__.startswith('/usr/'):
IN_DEVEL = True
if IN_DEVEL:
basedir = os.path.abspath('%s/../' % os.path.dirname(__file__))
if os.path.isdir(os.path.join(basedir, 'moulinette')):
sys.path.insert(0, basedir)
LOG_DIR = os.path.join(basedir, 'log')
# Initialization & helpers functions ----------------------------------- # Initialization & helpers functions -----------------------------------
@ -40,83 +41,121 @@ def _die(message, title='Error:'):
print('%s %s' % (colorize(title, 'red'), message)) print('%s %s' % (colorize(title, 'red'), message))
sys.exit(1) sys.exit(1)
def _check_in_devel(): def _parse_cli_args():
"""Check and load if needed development environment""" """Parse additional arguments for the cli"""
global IN_DEVEL, LOG_DIR import argparse
basedir = os.path.abspath('%s/../' % os.path.dirname(__file__))
if os.path.isdir('%s/moulinette' % basedir) and not IN_DEVEL:
# Add base directory to python path
sys.path.insert(0, basedir)
# Update global variables parser = argparse.ArgumentParser(add_help=False)
IN_DEVEL = True parser.add_argument('--no-cache',
LOG_DIR = '%s/log' % basedir action='store_false', default=True, dest='use_cache',
help="Don't use actions map cache",
)
parser.add_argument('--output-as',
choices=['json', 'plain'], default=None,
help="Output result in another format",
)
parser.add_argument('--debug',
action='store_true', default=False,
help="Log and print debug messages",
)
parser.add_argument('--verbose',
action='store_true', default=False,
help="Be more verbose in the output",
)
parser.add_argument('--quiet',
action='store_true', default=False,
help="Don't produce any output",
)
parser.add_argument('--admin-password',
default=None, dest='password', metavar='PASSWORD',
help="The admin password to use to authenticate",
)
# deprecated arguments
parser.add_argument('--plain',
action='store_true', default=False, help=argparse.SUPPRESS
)
parser.add_argument('--json',
action='store_true', default=False, help=argparse.SUPPRESS
)
def _parse_argv(): opts, args = parser.parse_known_args()
"""Parse additional arguments and return remaining ones"""
argv = list(sys.argv)
argv.pop(0)
if '--no-cache' in argv: # output compatibility
global USE_CACHE if opts.plain:
USE_CACHE = False opts.output_as = 'plain'
argv.remove('--no-cache') elif opts.json:
if '--json' in argv: opts.output_as = 'json'
global PRINT_JSON
PRINT_JSON = True
argv.remove('--json')
if '--plain' in argv:
global PRINT_PLAIN
PRINT_PLAIN = True
argv.remove('--plain')
if '--debug' in argv:
global LOGGERS_LEVEL
LOGGERS_LEVEL = 'DEBUG'
argv.remove('--debug')
if '--verbose' in argv:
global LOGGERS_HANDLERS
if 'console' not in LOGGERS_HANDLERS:
LOGGERS_HANDLERS.append('console')
argv.remove('--verbose')
return argv
def _init_moulinette(): return (parser, opts, args)
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 from moulinette import init
# Define loggers handlers
handlers = set(LOGGERS_HANDLERS)
if quiet and 'tty' in handlers:
handlers.remove('tty')
elif verbose and 'tty' not in handlers:
handlers.append('tty')
root_handlers = set(handlers)
if not debug:
root_handlers.remove('tty')
# Define loggers level
level = LOGGERS_LEVEL
tty_level = TTY_LOG_LEVEL
if verbose:
tty_level = 'INFO'
if debug:
tty_level = level = 'DEBUG'
# Custom logging configuration # Custom logging configuration
logging = { logging = {
'version': 1, 'version': 1,
'disable_existing_loggers': True, 'disable_existing_loggers': True,
'formatters': { 'formatters': {
'simple': { 'tty-debug': {
'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s - %(message)s' 'format': '%(relativeCreated)-4d %(fmessage)s'
}, },
'precise': { 'precise': {
'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(message)s' 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s'
},
},
'filters': {
'action': {
'()': 'moulinette.utils.log.ActionFilter',
}, },
}, },
'handlers': { 'handlers': {
'console': { 'tty': {
'class': 'logging.StreamHandler', 'level': tty_level,
'formatter': 'simple', 'class': 'moulinette.interfaces.cli.TTYHandler',
'stream': 'ext://sys.stderr', 'formatter': 'tty-debug' if debug else '',
}, },
'file': { 'file': {
'class': 'logging.FileHandler', 'class': 'logging.FileHandler',
'formatter': 'precise', 'formatter': 'precise',
'filename': '%s/%s' % (LOG_DIR, LOG_FILE), 'filename': '%s/%s' % (LOG_DIR, LOG_FILE),
'filters': ['action'],
}, },
}, },
'loggers': { 'loggers': {
'moulinette': {
'level': LOGGERS_LEVEL,
'handlers': LOGGERS_HANDLERS,
},
'yunohost': { 'yunohost': {
'level': LOGGERS_LEVEL, 'level': level,
'handlers': LOGGERS_HANDLERS, 'handlers': handlers,
'propagate': False,
}, },
'moulinette': {
'level': level,
'handlers': [],
'propagate': True,
},
},
'root': {
'level': level,
'handlers': root_handlers,
}, },
} }
@ -144,9 +183,8 @@ def _retrieve_namespaces():
# Main action ---------------------------------------------------------- # Main action ----------------------------------------------------------
if __name__ == '__main__': if __name__ == '__main__':
_check_in_devel() parser, opts, args = _parse_cli_args()
args = _parse_argv() _init_moulinette(opts.debug, opts.verbose, opts.quiet)
_init_moulinette()
# Check that YunoHost is installed # Check that YunoHost is installed
if not os.path.isfile('/etc/yunohost/installed') and \ if not os.path.isfile('/etc/yunohost/installed') and \
@ -163,6 +201,8 @@ if __name__ == '__main__':
# Execute the action # Execute the action
from moulinette import cli from moulinette import cli
ret = cli(_retrieve_namespaces(), args, use_cache=USE_CACHE, ret = cli(_retrieve_namespaces(), args,
print_json=PRINT_JSON, print_plain=PRINT_PLAIN) use_cache=opts.use_cache, output_as=opts.output_as,
password=opts.password, parser_kwargs={'top_parser': parser}
)
sys.exit(ret) sys.exit(ret)

View file

@ -7,24 +7,32 @@ import os.path
# Either we are in a development environment or not # Either we are in a development environment or not
IN_DEVEL = False IN_DEVEL = False
# Either cache has to be used inside the moulinette or not # Default server configuration
USE_CACHE = True DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 6787
# Either WebSocket has to be installed by the moulinette or not
USE_WEBSOCKET = True
# Level for which loggers will log # Level for which loggers will log
LOGGERS_LEVEL = 'INFO' LOGGERS_LEVEL = 'INFO'
# Handlers that will be used by loggers # Handlers that will be used by loggers
# - file: log to the file LOG_DIR/LOG_FILE # - file: log to the file LOG_DIR/LOG_FILE
# - api: serve logs through the api
# - console: log to stderr # - console: log to stderr
LOGGERS_HANDLERS = ['file'] LOGGERS_HANDLERS = ['file', 'api']
# Directory and file to be used by logging # Directory and file to be used by logging
LOG_DIR = '/var/log/yunohost' LOG_DIR = '/var/log/yunohost'
LOG_FILE = 'yunohost-api.log' LOG_FILE = 'yunohost-api.log'
# Check and load - as needed - development environment
if not __file__.startswith('/usr/'):
IN_DEVEL = True
if IN_DEVEL:
basedir = os.path.abspath('%s/../' % os.path.dirname(__file__))
if os.path.isdir(os.path.join(basedir, 'moulinette')):
sys.path.insert(0, basedir)
LOG_DIR = os.path.join(basedir, 'log')
# Initialization & helpers functions ----------------------------------- # Initialization & helpers functions -----------------------------------
@ -37,79 +45,112 @@ def _die(message, title='Error:'):
print('%s %s' % (colorize(title, 'red'), message)) print('%s %s' % (colorize(title, 'red'), message))
sys.exit(1) sys.exit(1)
def _check_in_devel(): def _parse_api_args():
"""Check and load if needed development environment""" """Parse main arguments for the api"""
global IN_DEVEL, LOG_DIR import argparse
basedir = os.path.abspath('%s/../' % os.path.dirname(__file__))
if os.path.isdir('%s/moulinette' % basedir) and not IN_DEVEL:
# Add base directory to python path
sys.path.insert(0, basedir)
# Update global variables parser = argparse.ArgumentParser(add_help=False,
IN_DEVEL = True description="Run the YunoHost API to manage your server.",
LOG_DIR = '%s/log' % basedir )
srv_group = parser.add_argument_group('server configuration')
srv_group.add_argument('-h', '--host',
action='store', default=DEFAULT_HOST,
help="Host to listen on (default: %s)" % DEFAULT_HOST,
)
srv_group.add_argument('-p', '--port',
action='store', default=DEFAULT_PORT, type=int,
help="Port to listen on (default: %d)" % DEFAULT_PORT,
)
srv_group.add_argument('--no-websocket',
action='store_true', default=True, dest='use_websocket',
help="Serve without WebSocket support, used to handle "
"asynchronous responses such as the messages",
)
glob_group = parser.add_argument_group('global arguments')
glob_group.add_argument('--no-cache',
action='store_false', default=True, dest='use_cache',
help="Don't use actions map cache",
)
glob_group.add_argument('--debug',
action='store_true', default=False,
help="Set log level to DEBUG",
)
glob_group.add_argument('--verbose',
action='store_true', default=False,
help="Be verbose in the output",
)
glob_group.add_argument('--help',
action='help', help="Show this help message and exit",
)
def _parse_argv(): return parser.parse_args()
"""Parse additional arguments and return remaining ones"""
argv = list(sys.argv)
argv.pop(0)
if '--no-cache' in argv: def _init_moulinette(use_websocket=True, debug=False, verbose=False):
global USE_CACHE
USE_CACHE = False
argv.remove('--no-cache')
if '--no-websocket' in argv:
global USE_WEBSOCKET
USE_WEBSOCKET = False
argv.remove('--no-websocket')
if '--debug' in argv:
global LOGGERS_LEVEL
LOGGERS_LEVEL = 'DEBUG'
argv.remove('--debug')
if '--verbose' in argv:
global LOGGERS_HANDLERS
if 'console' not in LOGGERS_HANDLERS:
LOGGERS_HANDLERS.append('console')
argv.remove('--verbose')
return argv
def _init_moulinette():
"""Configure logging and initialize the moulinette""" """Configure logging and initialize the moulinette"""
from moulinette import init from moulinette import init
# Define loggers handlers
handlers = set(LOGGERS_HANDLERS)
if not use_websocket and 'api' in handlers:
handlers.remove('api')
if verbose and 'console' not in handlers:
handlers.add('console')
root_handlers = handlers - set(['api'])
# Define loggers level
level = LOGGERS_LEVEL
if debug:
level = 'DEBUG'
# Custom logging configuration # Custom logging configuration
logging = { logging = {
'version': 1, 'version': 1,
'disable_existing_loggers': True, 'disable_existing_loggers': True,
'formatters': { 'formatters': {
'simple': { 'console': {
'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s - %(message)s' 'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s'
}, },
'precise': { 'precise': {
'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(message)s' 'format': '%(asctime)-15s %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s'
},
},
'filters': {
'action': {
'()': 'moulinette.utils.log.ActionFilter',
}, },
}, },
'handlers': { 'handlers': {
'console': { 'api': {
'class': 'logging.StreamHandler', 'class': 'moulinette.interfaces.api.APIQueueHandler',
'formatter': 'simple',
'stream': 'ext://sys.stderr',
}, },
'file': { 'file': {
'class': 'logging.handlers.WatchedFileHandler', 'class': 'logging.handlers.WatchedFileHandler',
'formatter': 'precise', 'formatter': 'precise',
'filename': '%s/%s' % (LOG_DIR, LOG_FILE), 'filename': '%s/%s' % (LOG_DIR, LOG_FILE),
'filters': ['action'],
},
'console': {
'class': 'logging.StreamHandler',
'formatter': 'console',
'stream': 'ext://sys.stdout',
'filters': ['action'],
}, },
}, },
'loggers': { 'loggers': {
'moulinette': {
'level': LOGGERS_LEVEL,
'handlers': LOGGERS_HANDLERS,
},
'yunohost': { 'yunohost': {
'level': LOGGERS_LEVEL, 'level': level,
'handlers': LOGGERS_HANDLERS, 'handlers': handlers,
'propagate': False,
}, },
'moulinette': {
'level': level,
'handlers': [],
'propagate': True,
},
},
'root': {
'level': level,
'handlers': root_handlers,
}, },
} }
@ -150,20 +191,18 @@ def is_installed():
# Main action ---------------------------------------------------------- # Main action ----------------------------------------------------------
if __name__ == '__main__': if __name__ == '__main__':
_check_in_devel() opts = _parse_api_args()
_parse_argv() _init_moulinette(opts.use_websocket, opts.debug, opts.verbose)
_init_moulinette()
from moulinette import (api, MoulinetteError) # Run the server
from moulinette import api, MoulinetteError
from yunohost import get_versions from yunohost import get_versions
try: ret = api(_retrieve_namespaces(),
# Run the server host=opts.host, port=opts.port,
api(_retrieve_namespaces(), port=6787,
routes={ routes={
('GET', '/installed'): is_installed, ('GET', '/installed'): is_installed,
('GET', '/version'): get_versions, ('GET', '/version'): get_versions,
}, },
use_cache=USE_CACHE, use_websocket=USE_WEBSOCKET) use_cache=opts.use_cache, use_websocket=opts.use_websocket
except MoulinetteError as e: )
_die(e.strerror, m18n.g('error')) sys.exit(ret)
sys.exit(0)

View file

@ -291,6 +291,18 @@ domain:
extra: extra:
pattern: *pattern_domain pattern: *pattern_domain
### domain_dns_conf()
dns-conf:
action_help: Generate DNS configuration for a domain
api: GET /domains/<domain>/dns
configuration:
lock: false
authenticate:
- api
arguments:
domain:
help: Target domain
### domain_info() ### domain_info()
# info: # info:
# action_help: Get domain informations # action_help: Get domain informations
@ -318,10 +330,10 @@ app:
arguments: arguments:
-u: -u:
full: --url full: --url
help: URL of remote JSON list (default http://fapp.yunohost.org/app/list/raw) help: URL of remote JSON list (default https://yunohost.org/official.json)
-n: -n:
full: --name full: --name
help: Name of the list (default fapp) help: Name of the list (default yunohost)
extra: extra:
pattern: &pattern_listname pattern: &pattern_listname
- !!str ^[a-z0-9_]+$ - !!str ^[a-z0-9_]+$
@ -505,6 +517,14 @@ app:
full: --sql full: --sql
help: Initial SQL file help: Initial SQL file
### app_debug()
debug:
action_help: Display all debug informations for an application
api: GET /apps/<app>/debug
arguments:
app:
help: App name
### app_makedefault() ### app_makedefault()
makedefault: makedefault:
action_help: Redirect domain root to an app action_help: Redirect domain root to an app
@ -1009,6 +1029,10 @@ firewall:
reload: reload:
action_help: Reload all firewall rules action_help: Reload all firewall rules
api: PUT /firewall api: PUT /firewall
arguments:
--skip-upnp:
help: Do not refresh port forwarding using UPnP
action: store_true
### firewall_allow() ### firewall_allow()
allow: allow:
@ -1189,9 +1213,12 @@ tools:
### tools_maindomain() ### tools_maindomain()
maindomain: maindomain:
action_help: Main domain change tool action_help: Main domain change tool
api: PUT /domains/main api:
- GET /domains/main
- PUT /domains/main
configuration: configuration:
authenticate: all authenticate: all
lock: false
arguments: arguments:
-o: -o:
full: --old-domain full: --old-domain
@ -1284,6 +1311,16 @@ hook:
app: app:
help: Scripts related to app will be removed help: Scripts related to app will be removed
### hook_info()
info:
action_help: Get information about a given hook
api: GET /hooks/<action>/<name>
arguments:
action:
help: Action name
name:
help: Hook name
### hook_list() ### hook_list()
list: list:
action_help: List available hooks for an action action_help: List available hooks for an action
@ -1320,21 +1357,20 @@ hook:
help: Ordered list of arguments to pass to the script help: Ordered list of arguments to pass to the script
nargs: "*" nargs: "*"
### hook_check()
check:
action_help: Parse the script file and get arguments
api: GET /hook/check
arguments:
file:
help: File to check
### 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 api: GET /hook
arguments: arguments:
file: path:
help: Script to execute help: Path of the script to execute
-a: -a:
full: --args full: --args
help: Arguments to pass to the script help: Arguments to pass to the script
--raise-on-error:
help: Raise if the script returns a non-zero exit code
action: store_true
-q:
full: --no-trace
help: Do not print each command that will be executed
action: store_true

View file

@ -27,3 +27,15 @@ ynh_bind_or_cp() {
fi fi
$SUDO_CMD cp -r "$SRCDIR" "$DESTDIR" $SUDO_CMD cp -r "$SRCDIR" "$DESTDIR"
} }
# Create a directory under /tmp
#
# usage: ynh_mkdir_tmp
# | ret: the created directory path
ynh_mkdir_tmp() {
TMPDIR="/tmp/$(ynh_string_random 6)"
while [ -d $TMPDIR ]; do
TMPDIR="/tmp/$(ynh_string_random 6)"
done
mkdir -p "$TMPDIR" && echo "$TMPDIR"
}

92
data/apps/helpers.d/mysql Normal file
View file

@ -0,0 +1,92 @@
MYSQL_ROOT_PWD_FILE=/etc/yunohost/mysql
# Open a connection as a user
#
# example: ynh_mysql_connect_as 'user' 'pass' <<< "UPDATE ...;"
# example: ynh_mysql_connect_as 'user' 'pass' < /path/to/file.sql
#
# usage: ynh_mysql_connect_as user pwd [db]
# | arg: user - the user name to connect as
# | arg: pwd - the user password
# | arg: db - the database to connect to
ynh_mysql_connect_as() {
mysql -u "$1" --password="$2" -B "${3:-}"
}
# Execute a command as root user
#
# usage: ynh_mysql_execute_as_root sql [db]
# | arg: sql - the SQL command to execute
# | arg: db - the database to connect to
ynh_mysql_execute_as_root() {
ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \
"${2:-}" <<< "$1"
}
# Execute a command from a file as root user
#
# usage: ynh_mysql_execute_file_as_root sql [db]
# | arg: file - the file containing SQL commands
# | arg: db - the database to connect to
ynh_mysql_execute_file_as_root() {
ynh_mysql_connect_as "root" "$(sudo cat $MYSQL_ROOT_PWD_FILE)" \
"${2:-}" < "$1"
}
# Create a database and grant optionnaly privilegies to a user
#
# usage: ynh_mysql_create_db db [user [pwd]]
# | arg: db - the database name to create
# | arg: user - the user to grant privilegies
# | arg: pwd - the password to identify user by
ynh_mysql_create_db() {
db=$1
sql="CREATE DATABASE ${db};"
# grant all privilegies to user
if [[ $# -gt 1 ]]; then
sql+=" GRANT ALL PRIVILEGES ON ${db}.* TO '${2}'@'localhost'"
[[ -n ${3:-} ]] && sql+=" IDENTIFIED BY '${3}'"
sql+=" WITH GRANT OPTION;"
fi
ynh_mysql_execute_as_root "$sql"
}
# Drop a database
#
# usage: ynh_mysql_drop_db db
# | arg: db - the database name to drop
ynh_mysql_drop_db() {
ynh_mysql_execute_as_root "DROP DATABASE ${1};"
}
# Dump a database
#
# example: ynh_mysql_dump_db 'roundcube' > ./dump.sql
#
# usage: ynh_mysql_dump_db db
# | arg: db - the database name to dump
# | ret: the mysqldump output
ynh_mysql_dump_db() {
mysqldump -u "root" -p"$(sudo cat $MYSQL_ROOT_PWD_FILE)" "$1"
}
# Create a user
#
# usage: ynh_mysql_create_user user pwd [host]
# | arg: user - the user name to create
# | arg: pwd - the password to identify user by
ynh_mysql_create_user() {
ynh_mysql_execute_as_root \
"CREATE USER '${1}'@'localhost' IDENTIFIED BY '${2}';"
}
# Drop a user
#
# usage: ynh_mysql_drop_user user
# | arg: user - the user name to drop
ynh_mysql_drop_user() {
ynh_mysql_execute_as_root "DROP USER '${1}'@'localhost';"
}

View file

@ -0,0 +1,88 @@
# Check either a package is installed or not
#
# example: ynh_package_is_installed 'yunohost' && echo "ok"
#
# usage: ynh_package_is_installed name
# | arg: name - the package name to check
ynh_package_is_installed() {
dpkg-query -W -f '${Status}' "$1" 2>/dev/null \
| grep -c "ok installed" &>/dev/null
}
# Get the version of an installed package
#
# example: version=$(ynh_package_version 'yunohost')
#
# usage: ynh_package_version name
# | arg: name - the package name to get version
# | ret: the version or an empty string
ynh_package_version() {
if ynh_package_is_installed "$1"; then
dpkg-query -W -f '${Version}' "$1" 2>/dev/null
else
echo ''
fi
}
# Update package index files
#
# usage: ynh_package_update
ynh_package_update() {
sudo apt-get -y -qq update
}
# Install package(s)
#
# usage: ynh_package_install name [name [...]]
# | arg: name - the package name to install
ynh_package_install() {
sudo apt-get -y -qq install $@
}
# Build and install a package from an equivs control file
#
# example: generate an empty control file with `equivs-control`, adjust its
# content and use helper to build and install the package:
# ynh_package_install_from_equivs /path/to/controlfile
#
# usage: ynh_package_install_from_equivs controlfile
# | arg: controlfile - path of the equivs control file
ynh_package_install_from_equivs() {
ynh_package_is_installed 'equivs' \
|| ynh_package_install equivs
# retrieve package information
pkgname=$(grep '^Package: ' $1 | cut -d' ' -f 2)
pkgversion=$(grep '^Version: ' $1 | cut -d' ' -f 2)
[[ -z "$pkgname" || -z "$pkgversion" ]] \
&& echo "Invalid control file" && exit 1
controlfile=$(readlink -f "$1")
# update packages cache
ynh_package_update
# build and install the package
TMPDIR=$(ynh_mkdir_tmp)
(cd $TMPDIR \
&& equivs-build "$controlfile" 1>/dev/null \
&& sudo dpkg --force-depends \
-i "./${pkgname}_${pkgversion}_all.deb" 2>&1 \
&& sudo apt-get -f -y -qq install) \
&& ([[ -n "$TMPDIR" ]] && rm -rf $TMPDIR)
}
# Remove package(s)
#
# usage: ynh_package_remove name [name [...]]
# | arg: name - the package name to remove
ynh_package_remove() {
sudo apt-get -y -qq remove $@
}
# Remove package(s) and their uneeded dependencies
#
# usage: ynh_package_autoremove name [name [...]]
# | arg: name - the package name to remove
ynh_package_autoremove() {
sudo apt-get -y -qq autoremove $@
}

View file

@ -1,3 +0,0 @@
ynh_password() {
echo $(dd if=/dev/urandom bs=1 count=200 2> /dev/null | tr -c -d '[A-Za-z0-9]' | sed -n 's/\(.\{24\}\).*/\1/p')
}

View file

@ -0,0 +1,27 @@
# Get an application setting
#
# usage: ynh_app_setting_get app key
# | arg: app - the application id
# | arg: key - the setting to get
ynh_app_setting_get() {
sudo yunohost app setting "$1" "$2" --output-as plain
}
# Set an application setting
#
# usage: ynh_app_setting_set app key value
# | arg: app - the application id
# | 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"
}
# Delete an application setting
#
# usage: ynh_app_setting_delete app key
# | arg: app - the application id
# | arg: key - the setting to delete
ynh_app_setting_delete() {
sudo yunohost app setting -d "$1" "$2"
}

View file

@ -0,0 +1,11 @@
# Generate a random string
#
# example: pwd=$(ynh_string_random 8)
#
# usage: ynh_string_random [length]
# | arg: length - the string length to generate (default: 24)
ynh_string_random() {
dd if=/dev/urandom bs=1 count=200 2> /dev/null \
| tr -c -d '[A-Za-z0-9]' \
| sed -n 's/\(.\{'"${1:-24}"'\}\).*/\1/p'
}

View file

@ -1,15 +1,29 @@
# Check if a user exists # Check if a YunoHost user exists
#
# example: ynh_user_exists 'toto' || exit 1
# #
# usage: ynh_user_exists username # usage: ynh_user_exists username
# | ret: retcode - 0 if user exists, 1 otherwise # | arg: username - the username to check
ynh_user_exists() { ynh_user_exists() {
sudo yunohost user list --json | grep -q "\"username\": \"${1}\"" sudo yunohost user list --output-as json | grep -q "\"username\": \"${1}\""
} }
# Retrieve a user information # Retrieve a YunoHost user information
# #
# usage: ynh_user_info username key # example: mail=$(ynh_user_get_info 'toto' 'mail')
#
# usage: ynh_user_get_info username key
# | arg: username - the username to retrieve info from
# | arg: key - the key to retrieve
# | ret: string - the key's value # | ret: string - the key's value
ynh_user_get_info() { ynh_user_get_info() {
sudo yunohost user info "${1}" --plain | ynh_get_plain_key "${2}" sudo yunohost user info "$1" --output-as plain | ynh_get_plain_key "$2"
}
# Check if a user exists on the system
#
# usage: ynh_system_user_exists username
# | arg: username - the username to check
ynh_system_user_exists() {
getent passwd "$1" &>/dev/null
} }

View file

@ -12,7 +12,7 @@ ynh_get_plain_key() {
[[ "$line" =~ ^${prefix}[^#] ]] && return [[ "$line" =~ ^${prefix}[^#] ]] && return
echo $line echo $line
elif [[ "$line" =~ ^${prefix}${key}$ ]]; then elif [[ "$line" =~ ^${prefix}${key}$ ]]; then
if [[ -n "$1" ]]; then if [[ -n "${1:-}" ]]; then
prefix+="#" prefix+="#"
key=$1 key=$1
shift shift

View file

@ -0,0 +1,12 @@
#
# Bash completion for yunohost
#
_python_argcomplete() {
local IFS=' '
COMPREPLY=( $(IFS="$IFS" COMP_LINE="$COMP_LINE" COMP_POINT="$COMP_POINT" _ARGCOMPLETE_COMP_WORDBREAKS="$COMP_WORDBREAKS" _ARGCOMPLETE=1 "$1" 8>&1 9>&2 1>/dev/null 2>/dev/null) )
if [[ $? != 0 ]]; then
unset COMPREPLY
fi
}
complete -o nospace -o default -F _python_argcomplete "yunohost"

View file

@ -1,15 +1,16 @@
backup_dir="$1/conf/ldap" #!/bin/bash
sudo mkdir -p $backup_dir
backup_dir="${1}/conf/ldap"
sudo mkdir -p "$backup_dir"
# Fix for first jessie yunohost where slapd.conf is called slapd-yuno.conf # Fix for first jessie yunohost where slapd.conf is called slapd-yuno.conf
# without slapcat doesn't work # without slapcat doesn't work
if [ ! -f /etc/ldap/slapd.conf ] [[ ! -f /etc/ldap/slapd.conf ]] \
then && sudo mv /etc/ldap/slapd-yuno.conf /etc/ldap/slapd.conf
sudo mv /etc/ldap/slapd-yuno.conf /etc/ldap/slapd.conf
fi
sudo cp -a /etc/ldap/slapd.conf $backup_dir/ # Back up the configuration
sudo cp -a /etc/ldap/slapd.conf "${backup_dir}/slapd.conf"
sudo slapcat -b cn=config -l "${backup_dir}/cn=config.master.ldif"
sudo slapcat -l $backup_dir/slapcat.ldif.raw # Back up the database
sudo bash -c "egrep -v '^entryCSN:' < $backup_dir/slapcat.ldif.raw > $backup_dir/slapcat.ldif" sudo slapcat -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif"
sudo rm -f $backup_dir/slapcat.ldif.raw

View file

@ -24,29 +24,49 @@ cd /usr/share/yunohost/templates/slapd
|| sudo yunohost service saferemove -s slapd \ || sudo yunohost service saferemove -s slapd \
/etc/ldap/slapd-yuno.conf /etc/ldap/slapd-yuno.conf
# Retrieve current backend
backend=$(sudo slapcat -n 0 | sed -n 's/^dn: olcDatabase={1}\(.*\),cn=config$/\1/p')
# Save current database in case of a backend change
BACKEND_CHANGE=0
BACKUP_DIR="/var/backups/dc=yunohost,dc=org-${backend}-$(date +%s)"
if [[ -n "$backend" && "$backend" != "mdb" && "$force" == "True" ]]; then
BACKEND_CHANGE=1
sudo mkdir -p "$BACKUP_DIR"
sudo slapcat -b dc=yunohost,dc=org \
-l "${BACKUP_DIR}/dc=yunohost-dc=org.ldif"
fi
safe_copy sudo.schema /etc/ldap/schema/sudo.schema safe_copy sudo.schema /etc/ldap/schema/sudo.schema
safe_copy mailserver.schema /etc/ldap/schema/mailserver.schema safe_copy mailserver.schema /etc/ldap/schema/mailserver.schema
safe_copy ldap.conf /etc/ldap/ldap.conf safe_copy ldap.conf /etc/ldap/ldap.conf
safe_copy slapd.default /etc/default/slapd safe_copy slapd.default /etc/default/slapd
# Compatibility: change from HDB to MDB on Jessie
version=$(sed 's/\..*//' /etc/debian_version)
if [[ "$version" == '8' ]]; then
cat slapd.conf \
| sed "s/hdb$/mdb/g" \
| sed "s/back_hdb/back_mdb/g" \
| sed "s/^dbconfig set_/#dbconfig set_/g" \
| sudo tee slapd.conf
fi
safe_copy slapd.conf /etc/ldap/slapd.conf safe_copy slapd.conf /etc/ldap/slapd.conf
# Fix some permissions
sudo chown root:openldap /etc/ldap/slapd.conf sudo chown root:openldap /etc/ldap/slapd.conf
sudo rm -Rf /etc/ldap/slapd.d
sudo mkdir /etc/ldap/slapd.d
sudo chown -R openldap:openldap /etc/ldap/schema/ sudo chown -R openldap:openldap /etc/ldap/schema/
sudo chown -R openldap:openldap /etc/ldap/slapd.d/ sudo chown -R openldap:openldap /etc/ldap/slapd.d/
sudo slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ # Check the slapd config file at first
sudo chown -R openldap:openldap /etc/ldap/slapd.d/ sudo slaptest -u -f /etc/ldap/slapd.conf
if [[ $BACKEND_CHANGE -eq 1 ]]; then
# Regenerate LDAP config directory and import database as root
# since the admin user may be unavailable
sudo sh -c "rm -Rf /etc/ldap/slapd.d;
mkdir /etc/ldap/slapd.d;
slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d;
chown -R openldap:openldap /etc/ldap/slapd.d;
slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org \
-l '${BACKUP_DIR}/dc=yunohost-dc=org.ldif';
chown -R openldap:openldap /var/lib/ldap" 2>&1
else
# Regenerate LDAP config directory from slapd.conf
sudo rm -Rf /etc/ldap/slapd.d
sudo mkdir /etc/ldap/slapd.d
sudo slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/ 2>&1
sudo chown -R openldap:openldap /etc/ldap/slapd.d/
fi
sudo service slapd force-reload sudo service slapd force-reload

View file

@ -18,30 +18,17 @@ function safe_copy () {
cd /usr/share/yunohost/templates/metronome cd /usr/share/yunohost/templates/metronome
# Copy additional modules
files="ldap.lib.lua
mod_auth_ldap2.lua
mod_legacyauth.lua
mod_storage_ldap.lua
vcard.lib.lua"
for file in $files; do
safe_copy modules/$file /usr/lib/metronome/modules/$file
done
# Copy configuration files # Copy configuration files
main_domain=$(cat /etc/yunohost/current_host) main_domain=$(cat /etc/yunohost/current_host)
cat metronome.cfg.lua.sed \ cat metronome.cfg.lua.sed \
| sed "s/{{ main_domain }}/$main_domain/g" \ | sed "s/{{ main_domain }}/$main_domain/g" \
| sudo tee metronome.cfg.lua | sudo tee metronome.cfg.lua
safe_copy metronome.cfg.lua /etc/metronome/metronome.cfg.lua safe_copy metronome.cfg.lua /etc/metronome/metronome.cfg.lua
safe_copy metronome.init /etc/init.d/metronome
safe_copy metronome.logrotate /etc/logrotate.d/metronome
need_restart=False need_restart=False
sudo mkdir -p /etc/metronome/conf.d sudo mkdir -p /etc/metronome/conf.d
domain_list=$(sudo yunohost domain list --plain) domain_list=$(sudo yunohost domain list --output-as plain)
# Copy a configuration file for each YunoHost domain # Copy a configuration file for each YunoHost domain
for domain in $domain_list; do for domain in $domain_list; do
@ -51,7 +38,7 @@ for domain in $domain_list; do
cat domain.cfg.lua.sed \ cat domain.cfg.lua.sed \
| sed "s/{{ domain }}/$domain/g" \ | sed "s/{{ domain }}/$domain/g" \
| sudo tee $domain.cfg.lua | sudo tee $domain.cfg.lua
if [[ $(safe_copy $domain.cfg.lua /etc/metronome/conf.d/$domain.cfg.lua) == "True" ]]; then if [[ $(safe_copy $domain.cfg.lua /etc/metronome/conf.d/$domain.cfg.lua | tail -n1) == "True" ]]; then
need_restart=True need_restart=True
fi fi
done done
@ -63,9 +50,8 @@ for file in /etc/metronome/conf.d/*; do
| sed 's|.cfg.lua||') | sed 's|.cfg.lua||')
sanitzed_domain="$(echo $domain | sed 's/\./%2e/g')" sanitzed_domain="$(echo $domain | sed 's/\./%2e/g')"
[[ $domain_list =~ $domain ]] \ [[ $domain_list =~ $domain ]] \
|| ($(sudo yunohost service saferemove -s metronome $file) == "True" \ || ([[ $(sudo yunohost service saferemove -s metronome $file | tail -n1) == "True" ]] \
&& rm -rf /var/lib/metronome/$sanitzed_domain) && rm -rf /var/lib/metronome/$sanitzed_domain)
done done
# Create domain directory # Create domain directory

View file

@ -37,7 +37,7 @@ done
if [ -f /etc/yunohost/installed ]; then if [ -f /etc/yunohost/installed ]; then
need_restart=False need_restart=False
domain_list=$(sudo yunohost domain list --plain) domain_list=$(sudo yunohost domain list --output-as plain)
# Copy a configuration file for each YunoHost domain # Copy a configuration file for each YunoHost domain
for domain in $domain_list; do for domain in $domain_list; do
@ -45,7 +45,7 @@ if [ -f /etc/yunohost/installed ]; then
cat server.conf.sed \ cat server.conf.sed \
| sed "s/{{ domain }}/$domain/g" \ | sed "s/{{ domain }}/$domain/g" \
| sudo tee $domain.conf | sudo tee $domain.conf
[[ $(safe_copy $domain.conf /etc/nginx/conf.d/$domain.conf) == "True" ]] \ [[ $(safe_copy $domain.conf /etc/nginx/conf.d/$domain.conf | tail -n1) == "True" ]] \
&& need_restart=True && need_restart=True
[ -f /etc/nginx/conf.d/$domain.d/yunohost_local.conf ] \ [ -f /etc/nginx/conf.d/$domain.d/yunohost_local.conf ] \

View file

@ -19,7 +19,7 @@ function safe_copy () {
cd /usr/share/yunohost/templates/postfix cd /usr/share/yunohost/templates/postfix
# Copy plain single configuration files # Copy plain single configuration files
files="header_check files="header_checks
ldap-accounts.cf ldap-accounts.cf
ldap-aliases.cf ldap-aliases.cf
ldap-domains.cf ldap-domains.cf

View file

@ -0,0 +1,35 @@
#!/bin/bash
set -e
# Execute this hook only if we force the configuration regeneration
if [[ "$1" == "True" ]]; then
# Add new email services
sudo yunohost service add rspamd -l /var/log/mail.log \
|| echo "Rspamd is already listed in services"
sudo yunohost service add rmilter -l /var/log/mail.log \
|| echo "Rmilter is already listed in services"
sudo yunohost service add memcached \
|| echo "Memcached is already listed in services"
# Remove previous email services
sudo yunohost service remove spamassassin \
|| echo "Spamassassin is already removed" \
&& sudo systemctl disable spamassassin || true
sudo rm -f etc/cron.daily/spamassassin
sudo yunohost service remove amavis \
|| echo "Amavis is already removed" \
&& sudo systemctl disable amavis || true
sudo yunohost service remove postgrey \
|| echo "Postgrey is already removed" \
&& sudo systemctl disable postgrey || true
sudo systemctl stop spamassassin || true
sudo systemctl stop amavis || true
sudo systemctl stop postgrey || true
fi

View file

@ -1,23 +0,0 @@
#!/bin/bash
set -e
force=$1
function safe_copy () {
if [[ "$force" == "True" ]]; then
sudo yunohost service safecopy \
-s postgrey \
$1 $2 \
--force
else
sudo yunohost service safecopy \
-s postgrey \
$1 $2
fi
}
cd /usr/share/yunohost/templates/postgrey
if [[ "$(safe_copy postgrey.default /etc/default/postgrey)" == "True" ]]; then
sudo service nslcd restart
fi

View file

@ -39,16 +39,14 @@ safe_copy dovecot-ldap.conf /etc/dovecot/dovecot-ldap.conf
# Setup Sieve # Setup Sieve
sudo rm -rf /etc/dovecot/global_script sudo mkdir -p /etc/dovecot/global_script
sudo mkdir -p -m 0770 /etc/dovecot/global_script sudo chmod -R 770 /etc/dovecot/global_script
safe_copy sa-learn-pipe.sh /usr/bin/sa-learn-pipe.sh
sudo chmod 755 /usr/bin/sa-learn-pipe.sh
safe_copy dovecot.sieve /etc/dovecot/global_script/dovecot.sieve safe_copy dovecot.sieve /etc/dovecot/global_script/dovecot.sieve
sudo chmod 660 /etc/dovecot/global_script/dovecot.sieve > /dev/null 2>&1 \ sudo chmod 660 /etc/dovecot/global_script/dovecot.sieve > /dev/null 2>&1 \
|| safe_copy dovecot.sieve /etc/dovecot/global_script/dovecot.sieve || safe_copy dovecot.sieve /etc/dovecot/global_script/dovecot.sieve
sudo sievec /etc/dovecot/global_script/dovecot.sieve sudo sievec /etc/dovecot/global_script/dovecot.sieve
sudo chmod 660 /etc/dovecot/global_script/dovecot.svbin sudo chmod 660 /etc/dovecot/global_script/dovecot.svbin
sudo chown -R vmail:mail /etc/dovecot/global_script
sudo service dovecot restart sudo service dovecot restart

View file

@ -0,0 +1,43 @@
#!/bin/bash
set -e
force=$1
function safe_copy () {
if [[ "$force" == "True" ]]; then
sudo yunohost service safecopy \
-s rmilter $1 $2 --force
else
sudo yunohost service safecopy \
-s rmilter $1 $2
fi
}
cd /usr/share/yunohost/templates/rmilter
# Copy Rmilter configuration
safe_copy rmilter.conf /etc/rmilter.conf
# Override socket configuration
safe_copy rmilter.socket /etc/systemd/system/rmilter.socket
# Create DKIM key for each YunoHost domain
sudo mkdir -p /etc/dkim
domain_list=$(sudo yunohost domain list --output-as plain)
for domain in $domain_list; do
[ -f /etc/dkim/$domain.mail.key ] \
|| (sudo opendkim-genkey --domain=$domain \
--selector=mail\
--directory=/etc/dkim \
&& sudo mv /etc/dkim/mail.private /etc/dkim/$domain.mail.key \
&& sudo mv /etc/dkim/mail.txt /etc/dkim/$domain.mail.txt)
sudo chown _rmilter /etc/dkim/$domain.mail.key
sudo chmod 400 /etc/dkim/$domain.mail.key
done
# Reload systemd daemon and stop rmilter service to take into account the
# new configuration. It will be started again by the socket as needed.
sudo systemctl daemon-reload
sudo systemctl stop rmilter.service 2>&1 || true

View file

@ -1,21 +0,0 @@
#!/bin/bash
set -e
force=$1
function safe_copy () {
if [[ "$force" == "True" ]]; then
sudo yunohost service safecopy \
-s spamassassin $1 $2 --force
else
sudo yunohost service safecopy \
-s spamassassin $1 $2
fi
}
cd /usr/share/yunohost/templates/spamassassin
safe_copy spamassassin.default /etc/default/spamassassin
safe_copy local.cf /etc/spamassassin/local.cf
sudo service spamassassin restart

View file

@ -1,37 +0,0 @@
#!/bin/bash
set -e
force=$1
function safe_copy () {
if [[ "$force" == "True" ]]; then
sudo yunohost service safecopy \
-s amavis $1 $2 --force
else
sudo yunohost service safecopy \
-s amavis $1 $2
fi
}
cd /usr/share/yunohost/templates/amavis
sudo mkdir -p /etc/amavis/conf.d/
# Copy plain single configuration files
files="05-domain_id
05-node_id
15-content_filter_mode
20-debian_defaults"
for file in $files; do
safe_copy $file /etc/amavis/conf.d/$file
done
main_domain=$(cat /etc/yunohost/current_host)
cat 50-user.sed \
| sed "s/{{ main_domain }}/$main_domain/g" \
| sudo tee 50-user
safe_copy 50-user /etc/amavis/conf.d/50-user
sudo service amavis restart

View file

@ -0,0 +1,28 @@
#!/bin/bash
set -e
force=$1
function safe_copy () {
if [[ "$force" == "True" ]]; then
sudo yunohost service safecopy \
-s rspamd $1 $2 --force
else
sudo yunohost service safecopy \
-s rspamd $1 $2
fi
}
cd /usr/share/yunohost/templates/rspamd
# Copy Rspamd configuration
safe_copy metrics.conf /etc/rspamd/metrics.conf
# Install Rspamd sieve script
safe_copy rspamd.sieve /etc/dovecot/global_script/rspamd.sieve
sudo sievec /etc/dovecot/global_script/rspamd.sieve
sudo chmod 660 /etc/dovecot/global_script/rspamd.svbin
sudo chown -R vmail:mail /etc/dovecot/global_script
sudo systemctl restart rspamd.socket
sudo systemctl restart dovecot

View file

@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
set -e set -e
force=$1 force=$1
@ -21,12 +21,12 @@ function randpass () {
cd /usr/share/yunohost/templates/mysql cd /usr/share/yunohost/templates/mysql
if [[ "$(safe_copy my.cnf /etc/mysql/my.cnf)" == "True" ]]; then if [[ "$(safe_copy my.cnf /etc/mysql/my.cnf | tail -n1)" == "True" ]]; then
sudo service mysql restart sudo service mysql restart
fi fi
if [ ! -f /etc/yunohost/mysql ]; then if [ ! -f /etc/yunohost/mysql ]; then
[[ $(/bin/ps aux | grep mysqld | grep -vc "grep") == "0" ]] \ [[ $(/bin/ps aux | grep '[m]ysqld') == "0" ]] \
&& sudo service mysql start && sudo service mysql start
mysql_password=$(randpass 10 0) mysql_password=$(randpass 10 0)

View file

@ -15,6 +15,6 @@ function safe_copy () {
cd /usr/share/yunohost/templates/avahi-daemon cd /usr/share/yunohost/templates/avahi-daemon
if [[ "$(safe_copy avahi-daemon.conf /etc/avahi/avahi-daemon.conf)" == "True" ]]; then if [[ "$(safe_copy avahi-daemon.conf /etc/avahi/avahi-daemon.conf | tail -n1)" == "True" ]]; then
sudo service avahi-daemon restart sudo service avahi-daemon restart
fi fi

View file

@ -15,6 +15,6 @@ function safe_copy () {
cd /usr/share/yunohost/templates/glances cd /usr/share/yunohost/templates/glances
if [[ "$(safe_copy glances.default /etc/default/glances)" == "True" ]]; then if [[ "$(safe_copy glances.default /etc/default/glances | tail -n1)" == "True" ]]; then
sudo service glances restart sudo service glances restart
fi fi

View file

@ -16,7 +16,7 @@ function safe_copy () {
cd /usr/share/yunohost/templates/dnsmasq cd /usr/share/yunohost/templates/dnsmasq
# Get IP address # Get IP address
ip=$(curl ip.yunohost.org || echo '0.0.0.0') ip=$(curl ip.yunohost.org 2>/dev/null || echo '0.0.0.0')
# Get IPv6 IP address # Get IPv6 IP address
ipv6=$(ip route get 2000:: | grep -q "unreachable" && echo '' \ ipv6=$(ip route get 2000:: | grep -q "unreachable" && echo '' \
@ -24,7 +24,7 @@ ipv6=$(ip route get 2000:: | grep -q "unreachable" && echo '' \
sudo mkdir -p /etc/dnsmasq.d sudo mkdir -p /etc/dnsmasq.d
domain_list=$(sudo yunohost domain list --plain) domain_list=$(sudo yunohost domain list --output-as plain)
# Copy a configuration file for each YunoHost domain # Copy a configuration file for each YunoHost domain
for domain in $domain_list; do for domain in $domain_list; do

View file

@ -15,6 +15,6 @@ function safe_copy () {
cd /usr/share/yunohost/templates/nsswitch cd /usr/share/yunohost/templates/nsswitch
if [[ "$(safe_copy nsswitch.conf /etc/nsswitch.conf)" == "True" ]]; then if [[ "$(safe_copy nsswitch.conf /etc/nsswitch.conf | tail -n1)" == "True" ]]; then
sudo service nscd restart sudo service nscd restart
fi fi

View file

@ -1,20 +0,0 @@
#!/bin/bash
set -e
force=$1
function safe_copy () {
if [[ "$force" == "True" ]]; then
sudo yunohost service safecopy \
-s udisks-glue $1 $2 --force
else
sudo yunohost service safecopy \
-s udisks-glue $1 $2
fi
}
cd /usr/share/yunohost/templates/udisks-glue
if [[ "$(safe_copy udisks-glue.conf /etc/udisks-glue.conf)" == "True" ]]; then
sudo service udisks-glue restart
fi

View file

@ -24,6 +24,6 @@ version=$(sed 's/\..*//' /etc/debian_version)
&& sudo cp jail-jessie.conf jail.conf \ && sudo cp jail-jessie.conf jail.conf \
|| sudo cp jail-wheezy.conf jail.conf || sudo cp jail-wheezy.conf jail.conf
if [[ $(safe_copy jail.conf /etc/fail2ban/jail.conf) == "True" ]]; then if [[ $(safe_copy jail.conf /etc/fail2ban/jail.conf | tail -n1) == "True" ]]; then
sudo service fail2ban restart sudo service fail2ban restart
fi fi

View file

@ -1,37 +1,60 @@
backup_dir="$1/conf/ldap" #!/bin/bash
if [ -z "$2" ]; then backup_dir="${1}/conf/ldap"
if [[ $EUID -ne 0 ]]; then
# We need to execute this script as root, since the ldap # We need to execute this script as root, since the ldap
# service will be shut down during the operation (and sudo # service will be shut down during the operation (and sudo
# won't be available) # won't be available)
sudo bash $(pwd)/$0 $1 sudoed sudo /bin/bash $(readlink -f $0) $1
else else
service slapd stop
# Backup old configuration service slapd stop || true
mv /var/lib/ldap /var/lib/ldap.old
# Recreate new DB folder # Create a directory for backup
mkdir /var/lib/ldap TMPDIR="/tmp/$(date +%s)"
chown openldap: /var/lib/ldap mkdir -p "$TMPDIR"
chmod go-rwx /var/lib/ldap
# Restore LDAP configuration (just to be sure) die() {
cp -a $backup_dir/slapd.conf /etc/ldap/slapd.conf state=$1
error=$2
# Regenerate the configuration # Restore saved configuration and database
rm -rf /etc/ldap/slapd.d/* [[ $state -ge 1 ]] \
slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d && (rm -rf /etc/ldap/slapd.d &&
chown -R openldap:openldap /etc/ldap/slapd.d mv "${TMPDIR}/slapd.d" /etc/ldap/slapd.d)
cp -rfp /var/lib/ldap.old/DB_CONFIG /var/lib/ldap [[ $state -ge 2 ]] \
&& (rm -rf /var/lib/ldap &&
mv "${TMPDIR}/ldap" /var/lib/ldap)
chown -R openldap: /etc/ldap/slapd.d /var/lib/ldap
# Import the database service slapd start
slapadd -l $backup_dir/slapcat.ldif rm -rf "$TMPDIR"
# Print an error message and exit
printf "%s" "$error" 1>&2
exit 1
}
# Restore the configuration
mv /etc/ldap/slapd.d "$TMPDIR"
mkdir -p /etc/ldap/slapd.d
cp -a "${backup_dir}/slapd.conf" /etc/ldap/slapd.conf
slapadd -F /etc/ldap/slapd.d -b cn=config \
-l "${backup_dir}/cn=config.master.ldif" \
|| die 1 "Unable to restore LDAP configuration"
chown -R openldap: /etc/ldap/slapd.d
# Restore the database
mv /var/lib/ldap "$TMPDIR"
mkdir -p /var/lib/ldap
slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org \
-l "${backup_dir}/dc=yunohost-dc=org.ldif" \
|| die 2 "Unable to restore LDAP database"
chown -R openldap: /var/lib/ldap
# Change permissions and restart slapd
chown openldap: /var/lib/ldap/*
service slapd start service slapd start
rm -rf /var/lib/ldap.old rm -rf "$TMPDIR"
fi fi

View file

@ -1,19 +0,0 @@
use strict;
# $mydomain is used just for convenience in the config files and it is not
# used internally by amavisd-new except in the default X_HEADER_LINE (which
# Debian overrides by default anyway).
#chomp($mydomain = `head -n 1 /etc/mailname`);
# amavisd-new needs to know which email domains are to be considered local
# to the administrative domain. Only emails to "local" domains are subject
# to certain functionality, such as the addition of spam tags.
#
# Default local domains to $mydomain and all subdomains. Remember to
# override or redefine this if $mydomain is changed later in the config
# sequence.
@local_domains_acl = ( ".$mydomain" );
1; # ensure a defined return

View file

@ -1,13 +0,0 @@
use strict;
# $myhostname is used by amavisd-new for node identification, and it is
# important to get it right (e.g. for ESMTP EHLO, loop detection, and so on).
#chomp($myhostname = `hostname --fqdn`);
# To manually set $myhostname, edit the following line with the correct Fully
# Qualified Domain Name (FQDN) and remove the # at the beginning of the line.
#
#$myhostname = "mail.example.com";
1; # ensure a defined return

View file

@ -1,23 +0,0 @@
use strict;
# You can modify this file to re-enable SPAM checking through spamassassin
# and to re-enable antivirus checking.
#
# Default antivirus checking mode
# Uncomment the two lines below to enable it back
#
#@bypass_virus_checks_maps = (
# \%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re);
#
# Default SPAM checking mode
# Uncomment the two lines below to enable it back
#
@bypass_spam_checks_maps = (
\%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re);
1; # ensure a defined return

View file

@ -1,216 +0,0 @@
use strict;
# ADMINISTRATORS:
# Debian suggests that any changes you need to do that should never
# be "updated" by the Debian package should be made in another file,
# overriding the settings in this file.
#
# The package will *not* overwrite your settings, but by keeping
# them separate, you will make the task of merging changes on these
# configuration files much simpler...
# see /usr/share/doc/amavisd-new/examples/amavisd.conf-default for
# a list of all variables with their defaults;
# see /usr/share/doc/amavisd-new/examples/amavisd.conf-sample for
# a traditional-style commented file
# [note: the above files were not converted to Debian settings!]
#
# for more details see documentation in /usr/share/doc/amavisd-new
# and at http://www.ijs.si/software/amavisd/amavisd-new-docs.html
$QUARANTINEDIR = "$MYHOME/virusmails";
$quarantine_subdir_levels = 1; # enable quarantine dir hashing
$log_recip_templ = undef; # disable by-recipient level-0 log entries
$DO_SYSLOG = 1; # log via syslogd (preferred)
$syslog_ident = 'amavis'; # syslog ident tag, prepended to all messages
$syslog_facility = 'mail';
$syslog_priority = 'debug'; # switch to info to drop debug output, etc
$enable_db = 1; # enable use of BerkeleyDB/libdb (SNMP and nanny)
$enable_global_cache = 1; # enable use of libdb-based cache if $enable_db=1
$inet_socket_port = 10024; # default listening socket
$sa_spam_subject_tag = '***SPAM*** ';
$sa_tag_level_deflt = undef; # add spam info headers if at, or above that level
$sa_tag2_level_deflt = 4.00; # add 'spam detected' headers at that level
$sa_kill_level_deflt = 20.00; # triggers spam evasive actions
$sa_dsn_cutoff_level = 10; # spam level beyond which a DSN is not sent
$sa_mail_body_size_limit = 200*1024; # don't waste time on SA if mail is larger
$sa_local_tests_only = 0; # only tests which do not require internet access?
$recipient_delimiter = '+';
@addr_extension_spam_maps = ('Junk');
# Quota limits to avoid bombs (like 42.zip)
$MAXLEVELS = 14;
$MAXFILES = 1500;
$MIN_EXPANSION_QUOTA = 100*1024; # bytes
$MAX_EXPANSION_QUOTA = 300*1024*1024; # bytes
# You should:
# Use D_DISCARD to discard data (viruses)
# Use D_BOUNCE to generate local bounces by amavisd-new
# Use D_REJECT to generate local or remote bounces by the calling MTA
# Use D_PASS to deliver the message
#
# Whatever you do, *NEVER* use D_REJECT if you have other MTAs *forwarding*
# mail to your account. Use D_BOUNCE instead, otherwise you are delegating
# the bounce work to your friendly forwarders, which might not like it at all.
#
# On dual-MTA setups, one can often D_REJECT, as this just makes your own
# MTA generate the bounce message. Test it first.
#
# Bouncing viruses is stupid, always discard them after you are sure the AV
# is working correctly. Bouncing real SPAM is also useless, if you cannot
# D_REJECT it (and don't D_REJECT mail coming from your forwarders!).
$final_virus_destiny = D_DISCARD; # (data not lost, see virus quarantine)
$final_banned_destiny = D_BOUNCE; # D_REJECT when front-end MTA
$final_spam_destiny = D_DISCARD;
$final_bad_header_destiny = D_PASS; # False-positive prone (for spam)
$enable_dkim_verification = 1; #disabled to prevent warning
$enable_dkim_signing =1;
$virus_admin = "postmaster\@$mydomain"; # due to D_DISCARD default
# Set to empty ("") to add no header
$X_HEADER_LINE = "Debian $myproduct_name at $mydomain";
# REMAINING IMPORTANT VARIABLES ARE LISTED HERE BECAUSE OF LONGER ASSIGNMENTS
#
# DO NOT SEND VIRUS NOTIFICATIONS TO OUTSIDE OF YOUR DOMAIN. EVER.
#
# These days, almost all viruses fake the envelope sender and mail headers.
# Therefore, "virus notifications" became nothing but undesired, aggravating
# SPAM. This holds true even inside one's domain. We disable them all by
# default, except for the EICAR test pattern.
#
@viruses_that_fake_sender_maps = (new_RE(
[qr'\bEICAR\b'i => 0], # av test pattern name
[qr/.*/ => 1], # true for everything else
));
@keep_decoded_original_maps = (new_RE(
# qr'^MAIL$', # retain full original message for virus checking (can be slow)
qr'^MAIL-UNDECIPHERABLE$', # recheck full mail if it contains undecipherables
qr'^(ASCII(?! cpio)|text|uuencoded|xxencoded|binhex)'i,
# qr'^Zip archive data', # don't trust Archive::Zip
));
# for $banned_namepath_re, a new-style of banned table, see amavisd.conf-sample
$banned_filename_re = new_RE(
# qr'^UNDECIPHERABLE$', # is or contains any undecipherable components
# block certain double extensions anywhere in the base name
qr'\.[^./]*\.(exe|vbs|pif|scr|bat|cmd|com|cpl|dll)\.?$'i,
qr'\{[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}\}?$'i, # Windows Class ID CLSID, strict
qr'^application/x-msdownload$'i, # block these MIME types
qr'^application/x-msdos-program$'i,
qr'^application/hta$'i,
# qr'^application/x-msmetafile$'i, # Windows Metafile MIME type
# qr'^\.wmf$', # Windows Metafile file(1) type
# qr'^message/partial$'i, qr'^message/external-body$'i, # rfc2046 MIME types
# [ qr'^\.(Z|gz|bz2)$' => 0 ], # allow any in Unix-compressed
# [ qr'^\.(rpm|cpio|tar)$' => 0 ], # allow any in Unix-type archives
# [ qr'^\.(zip|rar|arc|arj|zoo)$'=> 0 ], # allow any within such archives
# [ qr'^application/x-zip-compressed$'i => 0], # allow any within such archives
qr'.\.(exe|vbs|pif|scr|bat|cmd|com|cpl)$'i, # banned extension - basic
# qr'.\.(ade|adp|app|bas|bat|chm|cmd|com|cpl|crt|emf|exe|fxp|grp|hlp|hta|
# inf|ins|isp|js|jse|lnk|mda|mdb|mde|mdw|mdt|mdz|msc|msi|msp|mst|
# ops|pcd|pif|prg|reg|scr|sct|shb|shs|vb|vbe|vbs|
# wmf|wsc|wsf|wsh)$'ix, # banned ext - long
# qr'.\.(mim|b64|bhx|hqx|xxe|uu|uue)$'i, # banned extension - WinZip vulnerab.
qr'^\.(exe-ms)$', # banned file(1) types
# qr'^\.(exe|lha|tnef|cab|dll)$', # banned file(1) types
);
# See http://support.microsoft.com/default.aspx?scid=kb;EN-US;q262631
# and http://www.cknow.com/vtutor/vtextensions.htm
# ENVELOPE SENDER SOFT-WHITELISTING / SOFT-BLACKLISTING
@score_sender_maps = ({ # a by-recipient hash lookup table,
# results from all matching recipient tables are summed
# ## per-recipient personal tables (NOTE: positive: black, negative: white)
# 'user1@example.com' => [{'bla-mobile.press@example.com' => 10.0}],
# 'user3@example.com' => [{'.ebay.com' => -3.0}],
# 'user4@example.com' => [{'cleargreen@cleargreen.com' => -7.0,
# '.cleargreen.com' => -5.0}],
## site-wide opinions about senders (the '.' matches any recipient)
'.' => [ # the _first_ matching sender determines the score boost
new_RE( # regexp-type lookup table, just happens to be all soft-blacklist
[qr'^(bulkmail|offers|cheapbenefits|earnmoney|foryou)@'i => 5.0],
[qr'^(greatcasino|investments|lose_weight_today|market\.alert)@'i=> 5.0],
[qr'^(money2you|MyGreenCard|new\.tld\.registry|opt-out|opt-in)@'i=> 5.0],
[qr'^(optin|saveonlsmoking2002k|specialoffer|specialoffers)@'i => 5.0],
[qr'^(stockalert|stopsnoring|wantsome|workathome|yesitsfree)@'i => 5.0],
[qr'^(your_friend|greatoffers)@'i => 5.0],
[qr'^(inkjetplanet|marketopt|MakeMoney)\d*@'i => 5.0],
),
# read_hash("/var/amavis/sender_scores_sitewide"),
# This are some examples for whitelists, since envelope senders can be forged
# they are not enabled by default.
{ # a hash-type lookup table (associative array)
#'nobody@cert.org' => -3.0,
#'cert-advisory@us-cert.gov' => -3.0,
#'owner-alert@iss.net' => -3.0,
#'slashdot@slashdot.org' => -3.0,
#'securityfocus.com' => -3.0,
#'ntbugtraq@listserv.ntbugtraq.com' => -3.0,
#'security-alerts@linuxsecurity.com' => -3.0,
#'mailman-announce-admin@python.org' => -3.0,
#'amavis-user-admin@lists.sourceforge.net'=> -3.0,
#'amavis-user-bounces@lists.sourceforge.net' => -3.0,
#'spamassassin.apache.org' => -3.0,
#'notification-return@lists.sophos.com' => -3.0,
#'owner-postfix-users@postfix.org' => -3.0,
#'owner-postfix-announce@postfix.org' => -3.0,
#'owner-sendmail-announce@lists.sendmail.org' => -3.0,
#'sendmail-announce-request@lists.sendmail.org' => -3.0,
#'donotreply@sendmail.org' => -3.0,
#'ca+envelope@sendmail.org' => -3.0,
#'noreply@freshmeat.net' => -3.0,
#'owner-technews@postel.acm.org' => -3.0,
#'ietf-123-owner@loki.ietf.org' => -3.0,
#'cvs-commits-list-admin@gnome.org' => -3.0,
#'rt-users-admin@lists.fsck.com' => -3.0,
#'clp-request@comp.nus.edu.sg' => -3.0,
#'surveys-errors@lists.nua.ie' => -3.0,
#'emailnews@genomeweb.com' => -5.0,
#'yahoo-dev-null@yahoo-inc.com' => -3.0,
#'returns.groups.yahoo.com' => -3.0,
#'clusternews@linuxnetworx.com' => -3.0,
#lc('lvs-users-admin@LinuxVirtualServer.org') => -3.0,
#lc('owner-textbreakingnews@CNNIMAIL12.CNN.COM') => -5.0,
# soft-blacklisting (positive score)
#'sender@example.net' => 3.0,
#'.example.net' => 1.0,
},
], # end of site-wide tables
});
1; # ensure a defined return

View file

@ -1,30 +0,0 @@
use strict;
#
# Place your configuration directives here. They will override those in
# earlier files.
#
# See /usr/share/doc/amavisd-new/ for documentation and examples of
# the directives you can use in this file
#
$myhostname = "{{ main_domain }}";
$mydomain = "{{ main_domain }}";
# Enable LDAP support
$enable_ldap = 1;
# Default LDAP settings
$default_ldap = {
hostname => "127.0.0.1",
tls => 0,
version => 3,
base => "dc=yunohost,dc=org",
scope => "sub",
query_filter => "(&(objectClass=inetOrgPerson)(mail=%m))",
};
#------------ Do not modify anything below this line -------------
1; # ensure a defined return

View file

@ -57,12 +57,12 @@ plugin {
antispam_debug_target = syslog antispam_debug_target = syslog
antispam_verbose_debug = 0 antispam_verbose_debug = 0
antispam_backend = pipe antispam_backend = pipe
antispam_spam = Junk;SPAM
antispam_trash = Trash antispam_trash = Trash
antispam_spam = SPAM;Junk antispam_pipe_program = /usr/bin/rspamc
antispam_allow_append_to_spam = no antispam_pipe_program_args = -h;localhost:11334;-P;q1
antispam_pipe_program = /usr/bin/sa-learn-pipe.sh antispam_pipe_program_spam_arg = learn_spam
antispam_pipe_program_spam_arg = --spam antispam_pipe_program_notspam_arg = learn_ham
antispam_pipe_program_notspam_arg = --ham
} }
plugin { plugin {

View file

@ -1,9 +0,0 @@
#!/bin/sh
echo /usr/bin/sa-learn $* /tmp/sendmail-msg-$$.txt ;
echo "$$-start ($*)" >> /tmp/sa-learn-pipe.log ;
#echo $* > /tmp/sendmail-parms.txt ;
cat<&0 >> /tmp/sendmail-msg-$$.txt ;
/usr/bin/sa-learn $* /tmp/sendmail-msg-$$.txt ;
rm -f /tmp/sendmail-msg-$$.txt ;
echo "$$-end" >> /tmp/sa-learn-pipe.log ;
exit 0;

View file

@ -1,119 +0,0 @@
#! /bin/sh
### BEGIN INIT INFO
# Provides: metronome
# Required-Start: $network $local_fs $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Metronome XMPP Server
### END INIT INFO
set -e
# /etc/init.d/metronome: start and stop Metronome XMPP server
NAME=metronome
USER=metronome
DAEMON=/usr/bin/metronome
PIDPATH=/var/run/metronome
PIDFILE="$PIDPATH"/metronome.pid
NICE=
MAXFDS=
CPUSCHED=
IOSCHED=
test -x "$DAEMON" || exit 0
. /lib/lsb/init-functions
if [ -f /etc/default/metronome ] ; then
. /etc/default/metronome
fi
start_opts() {
test -z "$NICE" || echo -n " --nicelevel $NICE"
test -z "$CPUSCHED" || echo -n " --procsched $CPUSCHED"
test -z "$IOSCHED" || echo -n " --iosched $IOSCHED"
}
start_metronome () {
mkdir -p `dirname $PIDFILE`
chown metronome:adm `dirname $PIDFILE`
if start-stop-daemon --start --quiet --pidfile "$PIDFILE" \
--chuid "$USER" --oknodo --user "$USER" --name lua5.1 \
$(start_opts) --startas "$DAEMON";
then
return 0
else
return 1
fi
}
stop_metronome () {
if start-stop-daemon --stop --quiet --retry 30 \
--oknodo --pidfile "$PIDFILE" --user "$USER" --name lua5.1;
then
return 0
else
return 1
fi
}
signal_metronome () {
if start-stop-daemon --stop --quiet --pidfile "$PIDFILE" \
--user "$USER" --name lua5.1 --oknodo --signal $1;
then
return 0
else
return 1
fi
}
case "$1" in
start)
log_daemon_msg "Starting Metronome XMPP Server" "metronome"
if start_metronome; then
log_end_msg 0;
else
log_end_msg 1;
fi
;;
stop)
log_daemon_msg "Stopping Metronome XMPP Server" "metronome"
if stop_metronome; then
log_end_msg 0;
else
log_end_msg 1;
fi
;;
force-reload|restart)
log_daemon_msg "Restarting Metronome XMPP Server" "metronome"
stop_metronome
if start_metronome; then
log_end_msg 0;
else
log_end_msg 1;
fi
;;
reload)
log_daemon_msg "Reloading Metronome XMPP Server" "metronome"
if signal_metronome 1; then
log_end_msg 0;
else
log_end_msg 1;
fi
;;
status)
status_of_proc -p $PIDFILE $DAEMON $NAME
;;
*)
log_action_msg "Usage: /etc/init.d/metronome {start|stop|restart|reload|status}"
exit 1
esac
exit 0

View file

@ -1,11 +0,0 @@
/var/log/metronome/metronome.log /var/log/metronome/metronome.err {
daily
rotate 14
compress
create 640 metronome adm
postrotate
/etc/init.d/metronome reload > /dev/null
endscript
sharedscripts
missingok
}

View file

@ -1,81 +0,0 @@
-- vim:sts=4 sw=4
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2012 Rob Hoelz
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- http://code.google.com/p/prosody-modules/source/browse/mod_auth_ldap/mod_auth_ldap.lua
-- adapted to use common LDAP store
local ldap = module:require 'ldap';
local new_sasl = require 'util.sasl'.new;
local jsplit = require 'util.jid'.split;
if not ldap then
return;
end
local provider = {}
function provider.test_password(username, password)
return ldap.bind(username, password);
end
function provider.user_exists(username)
local params = ldap.getparams()
local filter = ldap.filter.combine_and(params.user.filter, params.user.usernamefield .. '=' .. username);
if params.user.usernamefield == 'mail' then
filter = ldap.filter.combine_and(params.user.filter, 'mail=' .. username .. '@*');
end
return ldap.singlematch {
base = params.user.basedn,
filter = filter,
};
end
function provider.get_password(username)
return nil, "Passwords unavailable for LDAP.";
end
function provider.set_password(username, password)
return nil, "Passwords unavailable for LDAP.";
end
function provider.create_user(username, password)
return nil, "Account creation/modification not available with LDAP.";
end
function provider.get_sasl_handler()
local testpass_authentication_profile = {
plain_test = function(sasl, username, password, realm)
return provider.test_password(username, password), true;
end,
mechanisms = { PLAIN = true },
};
return new_sasl(module.host, testpass_authentication_profile);
end
function provider.is_admin(jid)
local admin_config = ldap.getparams().admin;
if not admin_config then
return;
end
local ld = ldap:getconnection();
local username = jsplit(jid);
local filter = ldap.filter.combine_and(admin_config.filter, admin_config.namefield .. '=' .. username);
return ldap.singlematch {
base = admin_config.basedn,
filter = filter,
};
end
module:provides("auth", provider);

View file

@ -1,180 +0,0 @@
-- vim:sts=4 sw=4
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2012 Rob Hoelz
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
----------------------------------------
-- Constants and such --
----------------------------------------
local setmetatable = setmetatable;
local ldap = module:require 'ldap';
local vcardlib = module:require 'vcard';
local st = require 'util.stanza';
local gettime = require 'socket'.gettime;
if not ldap then
return;
end
local CACHE_EXPIRY = 300;
local params = module:get_option('ldap');
----------------------------------------
-- Utility Functions --
----------------------------------------
local function ldap_record_to_vcard(record)
return vcardlib.create {
record = record,
format = params.vcard_format,
}
end
local get_alias_for_user;
do
local user_cache;
local last_fetch_time;
local function populate_user_cache()
local ld = ldap.getconnection();
local usernamefield = params.user.usernamefield;
local namefield = params.user.namefield;
user_cache = {};
for _, attrs in ld:search { base = params.user.basedn, scope = 'onelevel', filter = params.user.filter } do
user_cache[attrs[usernamefield]] = attrs[namefield];
end
last_fetch_time = gettime();
end
function get_alias_for_user(user)
if last_fetch_time and last_fetch_time + CACHE_EXPIRY < gettime() then
user_cache = nil;
end
if not user_cache then
populate_user_cache();
end
return user_cache[user];
end
end
----------------------------------------
-- General Setup --
----------------------------------------
local ldap_store = {};
ldap_store.__index = ldap_store;
local adapters = {
roster = {},
vcard = {},
}
for k, v in pairs(adapters) do
setmetatable(v, ldap_store);
v.__index = v;
v.name = k;
end
function ldap_store:get(username)
return nil, "get method unimplemented on store '" .. tostring(self.name) .. "'"
end
function ldap_store:set(username, data)
return nil, "LDAP storage is currently read-only";
end
----------------------------------------
-- Roster Storage Implementation --
----------------------------------------
function adapters.roster:get(username)
local ld = ldap.getconnection();
local contacts = {};
local memberfield = params.groups.memberfield;
local namefield = params.groups.namefield;
local filter = memberfield .. '=' .. tostring(username);
local groups = {};
for _, config in ipairs(params.groups) do
groups[ config[namefield] ] = config.name;
end
-- XXX this kind of relies on the way we do groups at INOC
for _, attrs in ld:search { base = params.groups.basedn, scope = 'onelevel', filter = filter } do
if groups[ attrs[namefield] ] then
local members = attrs[memberfield];
for _, user in ipairs(members) do
if user ~= username then
local jid = user .. '@' .. module.host;
local record = contacts[jid];
if not record then
record = {
subscription = 'both',
groups = {},
name = get_alias_for_user(user),
};
contacts[jid] = record;
end
record.groups[ groups[ attrs[namefield] ] ] = true;
end
end
end
end
return contacts;
end
----------------------------------------
-- vCard Storage Implementation --
----------------------------------------
function adapters.vcard:get(username)
if not params.vcard_format then
return nil, '';
end
local ld = ldap.getconnection();
local filter = params.user.usernamefield .. '=' .. tostring(username);
local match = ldap.singlematch {
base = params.user.basedn,
filter = filter,
};
if match then
match.jid = username .. '@' .. module.host
return st.preserialize(ldap_record_to_vcard(match));
else
return nil, 'not found';
end
end
----------------------------------------
-- Driver Definition --
----------------------------------------
local driver = {};
function driver:open(store, typ)
local adapter = adapters[store];
if adapter and not typ then
return adapter;
end
return nil, "unsupported-store";
end
module:provides("storage", driver);

View file

@ -36,6 +36,9 @@ read_rnd_buffer_size = 256K
net_buffer_length = 2K net_buffer_length = 2K
thread_stack = 128K thread_stack = 128K
# to avoid corruption on powerfailure
default-storage-engine=innodb
# Don't listen on a TCP/IP port at all. This can be a security enhancement, # Don't listen on a TCP/IP port at all. This can be a security enhancement,
# if all processes that need to connect to mysqld run on the same host. # if all processes that need to connect to mysqld run on the same host.
# All interaction with mysqld must be made via Unix sockets or named pipes. # All interaction with mysqld must be made via Unix sockets or named pipes.

View file

@ -2,4 +2,10 @@ location /yunohost/admin {
alias /usr/share/yunohost/admin/; alias /usr/share/yunohost/admin/;
default_type text/html; default_type text/html;
index index.html; index index.html;
# Short cache on handlebars templates
location ~* \.(?:ms)$ {
expires 5m;
add_header Cache-Control "public";
}
} }

View file

@ -1,2 +1,2 @@
sub_filter <head> '<head><script type="text/javascript" src="/ynhpanel.js"></script>'; sub_filter </head> '<script type="text/javascript" src="/ynhpanel.js"></script></head>';
sub_filter_once on; sub_filter_once on;

View file

@ -15,24 +15,11 @@ base dc=yunohost,dc=org
# The LDAP protocol version to use. # The LDAP protocol version to use.
#ldap_version 3 #ldap_version 3
# The DN to bind with for normal lookups.
#binddn cn=annonymous,dc=example,dc=net
#bindpw secret
# The DN used for password modifications by root.
#rootpwmoddn cn=admin,dc=example,dc=com
# SSL options
#ssl off
#tls_reqcert never
tls_cacertfile /etc/ssl/certs/ca-certificates.crt
# The search scope. # The search scope.
#scope sub #scope sub
# Build a full list of non-LDAP users on startup.
nss_initgroups_ignoreusers ALLLOCAL
# The minimum numeric user id to lookup.
nss_min_uid 1000

View file

@ -86,9 +86,6 @@ smtpd_sasl_security_options = noanonymous
smtpd_sasl_local_domain = smtpd_sasl_local_domain =
# Use AMaVis
content_filter = amavis:[127.0.0.1]:10024
# Wait until the RCPT TO command before evaluating restrictions # Wait until the RCPT TO command before evaluating restrictions
smtpd_delay_reject = yes smtpd_delay_reject = yes
@ -128,13 +125,8 @@ smtpd_recipient_restrictions =
reject_non_fqdn_recipient, reject_non_fqdn_recipient,
reject_unknown_recipient_domain, reject_unknown_recipient_domain,
reject_unauth_destination, reject_unauth_destination,
check_policy_service unix:private/policy-spf
check_policy_service inet:127.0.0.1:10023
permit permit
# Use SPF
policy-spf_time_limit = 3600s
# SRS # SRS
sender_canonical_maps = regexp:/etc/postfix/sender_canonical sender_canonical_maps = regexp:/etc/postfix/sender_canonical
sender_canonical_classes = envelope_sender sender_canonical_classes = envelope_sender
@ -143,3 +135,11 @@ sender_canonical_classes = envelope_sender
smtp_header_checks = regexp:/etc/postfix/header_checks smtp_header_checks = regexp:/etc/postfix/header_checks
smtp_reply_filter = pcre:/etc/postfix/smtp_reply_filter smtp_reply_filter = pcre:/etc/postfix/smtp_reply_filter
# Rmilter
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
milter_protocol = 6
smtpd_milters = inet:localhost:11000
# Skip email without checking if milter has died
milter_default_action = accept

View file

@ -116,32 +116,3 @@ dovecot unix - n n - - pipe
# (yes) (yes) (yes) (never) (100) # (yes) (yes) (yes) (never) (100)
# ========================================================================== # ==========================================================================
# Added using postfix-add-filter script: # Added using postfix-add-filter script:
amavis unix - - - - 2 smtp
-o smtp_data_done_timeout=1200
-o smtp_send_xforward_command=yes
-o smtp_tls_note_starttls_offer=no
policy-spf unix - n n - - spawn
user=nobody argv=/usr/bin/perl /usr/sbin/postfix-policyd-spf-perl
127.0.0.1:10025 inet n - - - - smtpd
-o content_filter=
-o smtpd_delay_reject=no
-o smtpd_client_restrictions=permit_mynetworks,reject
-o smtpd_helo_restrictions=
-o smtpd_sender_restrictions=
-o smtpd_recipient_restrictions=permit_mynetworks,reject
-o smtpd_data_restrictions=reject_unauth_pipelining
-o smtpd_end_of_data_restrictions=
-o smtpd_restriction_classes=
-o mynetworks=127.0.0.0/8
-o smtpd_error_sleep_time=0
-o smtpd_soft_error_limit=1001
-o smtpd_hard_error_limit=1000
-o smtpd_client_connection_count_limit=0
-o smtpd_client_connection_rate_limit=0
-o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters
-o local_header_rewrite_clients=
-o smtpd_milters=
-o local_recipient_maps=
-o relay_recipient_maps=

View file

@ -1,12 +0,0 @@
# postgrey startup options, created for Debian
# you may want to set
# --delay=N how long to greylist, seconds (default: 300)
# --max-age=N delete old entries after N days (default: 35)
# see also the postgrey(8) manpage
POSTGREY_OPTS="--inet=10023 --delay=30"
# the --greylist-text commandline argument can not be easily passed through
# POSTGREY_OPTS when it contains spaces. So, insert your text here:
#POSTGREY_TEXT="Your customized rejection message here"

View file

@ -0,0 +1,18 @@
# systemd-specific settings for rmilter
.include /etc/rmilter.conf.common
# pidfile - path to pid file
pidfile = /run/rmilter/rmilter.pid;
# rmilter is socket-activated under systemd
bind_socket = fd:3;
# DKIM signing
dkim {
domain {
key = /etc/dkim;
domain = "*";
selector = "mail";
};
};

View file

@ -0,0 +1,5 @@
.include /lib/systemd/system/rmilter.socket
[Socket]
ListenStream=
ListenStream=127.0.0.1:11000

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
require ["fileinto"];
if header :is "X-Spam" "yes" {
fileinto "Junk";
}

View file

@ -22,14 +22,15 @@ pidfile /var/run/slapd/slapd.pid
# List of arguments that were passed to the server # List of arguments that were passed to the server
argsfile /var/run/slapd/slapd.args argsfile /var/run/slapd/slapd.args
password-hash {SSHA}
# Read slapd.conf(5) for possible values # Read slapd.conf(5) for possible values
loglevel 256 loglevel none
# Hashes to be used in generation of user passwords
password-hash {SSHA}
# Where the dynamically loaded modules are stored # Where the dynamically loaded modules are stored
modulepath /usr/lib/ldap modulepath /usr/lib/ldap
moduleload back_hdb moduleload back_mdb
moduleload memberof moduleload memberof
# The maximum number of entries that is returned for a search operation # The maximum number of entries that is returned for a search operation
@ -40,52 +41,31 @@ sizelimit 500
tool-threads 1 tool-threads 1
####################################################################### #######################################################################
# Specific Backend Directives for hdb: # Specific Backend Directives for mdb:
# Backend specific directives apply to this backend until another # Backend specific directives apply to this backend until another
# 'backend' directive occurs # 'backend' directive occurs
backend hdb backend mdb
####################################################################### #######################################################################
# Specific Backend Directives for 'other': # Specific Directives for database #1, of type mdb:
# Backend specific directives apply to this backend until another
# 'backend' directive occurs
#backend <other>
#######################################################################
# Specific Directives for database #1, of type hdb:
# Database specific directives apply to this databasse until another # Database specific directives apply to this databasse until another
# 'database' directive occurs # 'database' directive occurs
database hdb database mdb
# The base of your directory in database #1 # The base of your directory in database #1
suffix "dc=yunohost,dc=org" suffix "dc=yunohost,dc=org"
# rootdn directive for specifying a superuser on the database. This is needed
# for syncrepl.
# rootdn "cn=admin,dc=yunohost,dc=org"
# Where the database file are physically stored for database #1
directory "/var/lib/ldap" directory "/var/lib/ldap"
# The dbconfig settings are used to generate a DB_CONFIG file the first
# time slapd starts. They do NOT override existing an existing DB_CONFIG
# file. You should therefore change these settings in DB_CONFIG directly
# or remove DB_CONFIG and restart slapd for changes to take effect.
# For the Debian package we use 2MB as default but be sure to update this
# value if you have plenty of RAM
dbconfig set_cachesize 0 2097152 0
# Sven Hartge reported that he had to set this value incredibly high
# to get slapd running at all. See http://bugs.debian.org/303057 for more
# information.
# Number of objects that can be locked at the same time.
dbconfig set_lk_max_objects 1500
# Number of locks (both requested and granted)
dbconfig set_lk_max_locks 1500
# Number of lockers
dbconfig set_lk_max_lockers 1500
# Indexing options for database #1 # Indexing options for database #1
index objectClass eq index objectClass eq
index uid eq,sub index uid eq,sub
index entryCSN,entryUUID eq index entryCSN,entryUUID eq
# Save the time that the entry gets modified, for database #1 # Save the time that the entry gets modified, for database #1
lastmod on lastmod on
@ -94,26 +74,25 @@ lastmod on
# failure and to speed slapd shutdown. # failure and to speed slapd shutdown.
checkpoint 512 30 checkpoint 512 30
# Where to store the replica logs for database #1
# replogfile /var/lib/ldap/replog
# The userPassword by default can be changed # The userPassword by default can be changed
# by the entry owning it if they are authenticated. # by the entry owning it if they are authenticated.
# Others should not be able to see it, except the # Others should not be able to see it, except the
# admin entry below # admin entry below
# These access lines apply to database #1 only # These access lines apply to database #1 only
access to attrs=userPassword access to attrs=userPassword,shadowLastChange
by dn="cn=admin,dc=yunohost,dc=org" write by dn="cn=admin,dc=yunohost,dc=org" write
by anonymous auth by anonymous auth
by self write by self write
by * none by * none
# Personnal information can be changed by the entry
# owning it if they are authenticated.
# Others should be able to see it.
access to attrs=cn,gecos,givenName,mail,maildrop,displayName,sn access to attrs=cn,gecos,givenName,mail,maildrop,displayName,sn
by dn="cn=admin,dc=yunohost,dc=org" write by dn="cn=admin,dc=yunohost,dc=org" write
by self write by self write
by * read by * read
# Ensure read access to the base for things like # Ensure read access to the base for things like
# supportedSASLMechanisms. Without this you may # supportedSASLMechanisms. Without this you may
# have problems with SASL not knowing what # have problems with SASL not knowing what
@ -129,14 +108,5 @@ access to dn.base="" by * read
# can read everything. # can read everything.
access to * access to *
by dn="cn=admin,dc=yunohost,dc=org" write by dn="cn=admin,dc=yunohost,dc=org" write
by group/groupOfNames/Member="cn=admin,ou=groups,dc=yunohost,dc=org" write by group/groupOfNames/Member="cn=admin,ou=groups,dc=yunohost,dc=org" write
by * read by * read
#######################################################################
# Specific Directives for database #2, of type 'other' (can be hdb too):
# Database specific directives apply to this databasse until another
# 'database' directive occurs
#database <other>
# The base of your directory for database #2
#suffix "dc=debian,dc=org"

View file

@ -1,94 +0,0 @@
# This is the right place to customize your installation of SpamAssassin.
report_safe 0
lock_method flock
# Bayes-related operations
use_bayes 1
use_bayes_rules 1
bayes_auto_learn 1
bayes_auto_expire 1
bayes_path /var/lib/amavis/.spamassassin/bayes
bayes_file_mode 0777
# External network tests
dns_available yes
skip_rbl_checks 0
use_razor2 1
use_pyzor 1
# Use URIBL (http://www.uribl.com/about.shtml)
urirhssub URIBL_BLACK multi.uribl.com. A 2
body URIBL_BLACK eval:check_uridnsbl('URIBL_BLACK')
describe URIBL_BLACK Contains an URL listed in the URIBL blacklist
tflags URIBL_BLACK net
score URIBL_BLACK 3.0
urirhssub URIBL_GREY multi.uribl.com. A 4
body URIBL_GREY eval:check_uridnsbl('URIBL_GREY')
describe URIBL_GREY Contains an URL listed in the URIBL greylist
tflags URIBL_GREY net
score URIBL_GREY 0.25
# Use SURBL (http://www.surbl.org/)
urirhssub URIBL_JP_SURBL multi.surbl.org. A 64
body URIBL_JP_SURBL eval:check_uridnsbl('URIBL_JP_SURBL')
describe URIBL_JP_SURBL Has URI in JP at http://www.surbl.org/lists.html
tflags URIBL_JP_SURBL net
score URIBL_JP_SURBL 3.0
score SPF_FAIL 10.000
score SPF_HELO_FAIL 10.000
score RAZOR2_CHECK 2.500
score RAZOR2_CF_RANGE_51_100 3.500
#
# See 'perldoc Mail::SpamAssassin::Conf' for details of what can be
# tweaked.
#
# Only a small subset of options are listed below
#
###########################################################################
# Add *****SPAM***** to the Subject header of spam e-mails
#
# rewrite_header Subject *****SPAM*****
# Save spam messages as a message/rfc822 MIME attachment instead of
# modifying the original message (0: off, 2: use text/plain instead)
#
# report_safe 1
# Set which networks or hosts are considered 'trusted' by your mail
# server (i.e. not spammers)
#
# trusted_networks 212.17.35.
# Set file-locking method (flock is not safe over NFS, but is faster)
#
# lock_method flock
# Set the threshold at which a message is considered spam (default: 5.0)
#
# required_score 5.0
# Use Bayesian classifier (default: 1)
#
# use_bayes 1
# Bayesian classifier auto-learning (default: 1)
#
# bayes_auto_learn 1
# Set headers which may provide inappropriate cues to the Bayesian
# classifier
#
# bayes_ignore_header X-Bogosity
# bayes_ignore_header X-Spam-Flag
# bayes_ignore_header X-Spam-Status

View file

@ -1,31 +0,0 @@
# /etc/default/spamassassin
# Duncan Findlay
# WARNING: please read README.spamd before using.
# There may be security risks.
# Change to one to enable spamd
ENABLED=0
# Options
# See man spamd for possible options. The -d option is automatically added.
# SpamAssassin uses a preforking model, so be careful! You need to
# make sure --max-children is not set to anything higher than 5,
# unless you know what you're doing.
OPTIONS="--create-prefs --max-children 5 --helper-home-dir"
# Pid file
# Where should spamd write its PID to file? If you use the -u or
# --username option above, this needs to be writable by that user.
# Otherwise, the init script will not be able to shut spamd down.
PIDFILE="/var/run/spamd.pid"
# Set nice level of spamd
#NICE="--nicelevel 15"
# Cronjob
# Set to anything but 0 to enable the cron job to automatically update
# spamassassin's rules on a nightly basis
CRON=1

View file

@ -1,9 +0,0 @@
filter disks {
optical = false
partition_table = false
usage = filesystem
}
match disks {
automount=true
automount_options= { sync, noatime, "dmask=0", "fmask=0" }
}

View file

@ -13,6 +13,10 @@ dovecot:
postfix: postfix:
status: service status: service
log: [/var/log/mail.log,/var/log/mail.err] log: [/var/log/mail.log,/var/log/mail.err]
rmilter:
status: systemctl status rmilter.socket
rspamd:
status: systemctl status rspamd.socket
mysql: mysql:
status: service status: service
log: [/var/log/mysql.log,/var/log/mysql.err] log: [/var/log/mysql.log,/var/log/mysql.err]
@ -32,11 +36,10 @@ php5-fpm:
log: /var/log/php5-fpm.log log: /var/log/php5-fpm.log
yunohost-api: yunohost-api:
status: service status: service
log: /var/log/yunohost.log log: /var/log/yunohost/yunohost-api.log
postgrey: yunohost-firewall:
status: service status: service
log: /var/log/mail.log postgrey:
amavis:
status: service status: service
log: /var/log/mail.log log: /var/log/mail.log
nslcd: nslcd:
@ -44,8 +47,5 @@ nslcd:
log: /var/log/syslog log: /var/log/syslog
nsswitch: nsswitch:
status: service status: service
spamassassin: udisks2:
status: service
log: /var/log/mail.log
udisks-glue:
status: service status: service

172
debian/changelog vendored
View file

@ -1,3 +1,175 @@
yunohost (2.3.7) testing; urgency=low
[ Laurent Peuch ]
* [enh] new command to generate DNS configuration for a given domain name
[ Jérôme Lebleu ]
* [fix] Save LDAP database when switching to MDB (bugfix #169)
* [fix] Review LDAP backup and restore hooks
* [enh] Replace msignals.display by logging in backup category
* [enh] Add a ynh_app_setting_delete helper
* [enh] Update rmilter hook and dependencies for 1.7 release
* [enh] Set minimum uid and ignore local users in nslcd.conf
* [enh] Use a common function to retrieve app settings
* [enh] Check the slapd config file at first in conf_regen
* [fix] Validate arguments and app settings in app_map (bugfix #168)
* [fix] Replace udisks-glue by udisks2 and only suggest it
* [fix] Correct condition syntax in metronome conf_regen hook
* [fix] Allow false and 0 as non-empty values for an app argument
* [fix] Some improvements and fixes to actions related to app access
* [fix] Remove old services and add rmilter/rspamd
* [fix] Correct log file of yunohost-api in services.yml
* [i18n] Use named variables in app category translations
-- Jérôme Lebleu <jerome@yunohost.org> Sun, 07 Feb 2016 18:56:13 +0100
yunohost (2.3.6) testing; urgency=low
[ Jérôme Lebleu ]
* [enh] Pass app id to scripts and remove hook_check action
* [enh] Rely only on app_id argument for multi-instances apps
* [enh] Add support for app argument 'type' defined in the manifest
* [enh] Integrate 'optional' key of arguments in app manifest
* [enh] Implement 'boolean' argument type support in app manifest
* [enh] Add ping util as recommended package
* [enh] Add a helper to check if a user exists on the system
* [enh] Provide bash helpers for packages manipulation (wip #97)
* [enh] Add ynh_package_update helper and call it in install_from_equivs
* [fix] Do not block while set main domain
* [fix] Add GRANT OPTION in ynh_mysql_create_db helper
* [fix] Validate app argument choice for input value too
* [fix] Log rotation is already handled by WatchedFileHandler (fixbug #137)
* [fix] Use rmilter as a socket-activated service
* [fix] Parse app arguments before creating app folder and settings
* [fix] Use INFO logging level if app setting is not found
* [fix] Split service_configuration_conflict translation key (fixbug #136)
* [fix] Set default value of boolean argument type to false if unset
* [fix] Remove useless SPF setting in Postfix configuration (fixbug #150)
* [fix] Add procmail to packages dependencies
* [i18n] Review translations and keys related to app arguments
[ Sebastien Badia ]
* hooks: Use a more elegant grep command for mysql process check
-- Jérôme Lebleu <jerome@yunohost.org> Sun, 17 Jan 2016 02:57:53 +0100
yunohost (2.3.5) testing; urgency=low
[ opi ]
* [enh] Get app label for installed app in app list
* [enh] Short cache on handlebars templates
[ Jérôme Lebleu ]
* [enh] Allow to pass the admin password as argument in the cli
* [enh] Add main domain GET route
* [enh] Provide bash helpers for MySQL databases and app settings (wip #97)
* [enh] Rename ynh_password bash helper to ynh_string_random
* [fix] Check app min_version with yunohost package (fixbug #113)
* [fix] Use --output-as instead of deprecated options
* [fix] Prevent error if unset variable is treated in utils helper
* [doc] Improve usage and add examples for user helpers
* [i18n] Update translations from Transifex belatedly
-- Jérôme Lebleu <jerome@yunohost.org> Thu, 24 Dec 2015 10:55:36 +0100
yunohost (2.3.4) testing; urgency=low
[ Jérôme Lebleu ]
* [enh] Make use of call_async_output in hook_exec to get output in real time
* [fix] Display a more detailed message when yunohost-firewall is stopped
* [fix] Prevent insserv warning when using systemd at package postinst
* [fix] Log real exception string error in hook_callback
* [fix] Add yunohost-firewall.service but do not enable it
[ julienmalik ]
* [fix] Log for rmilter instead of rspamd
* [fix] Do not exit at first service which can't be stopped
-- Jérôme Lebleu <jerome.lebleu@mailoo.org> Tue, 17 Nov 2015 11:10:42 +0100
yunohost (2.3.3) testing; urgency=low
* [fix] Do not modify handlers with root_handlers in bin/yunohost
-- Jérôme Lebleu <jerome.lebleu@mailoo.org> Sun, 15 Nov 2015 15:00:04 +0100
yunohost (2.3.2) testing; urgency=low
[ Jérôme Lebleu ]
* [fix] Do not rely on dh_installinit and restart service after upgrade
* [fix] Add tty in root handlers if debug is set in bin/yunohost
[ kload ]
* [fix] Do not remove the global_script directory
* [fix] Unexpected warnings comming from stderr
* [enh] Warn the user about the waiting at the configuration generation
* [fix] Delayed upgrade of the package 'yunohost'
-- Jérôme Lebleu <jerome.lebleu@mailoo.org> Sun, 15 Nov 2015 14:03:39 +0100
yunohost (2.3.1) testing; urgency=low
[ Jérôme Lebleu ]
* [enh] Add logrotate configuration
* [enh] Allow to set default options for yunohost-api service
* [enh] Add bash completion for bin/yunohost
* [enh] Make use of new logging facilities in firewall, hook and service
* [enh] Refactor bin/yunohost and bin/yunohost-api to follow moulinette
changes and provide help for global arguments
* [enh] Split stdout/stderr wrapping in hook_exec and add a no_trace option
* [fix] Create home directory during login (fixbug #80)
* [fix] Keep compat with deprecated --plain and --json in the cli
* [fix] Do not restrict warning to tty in service_saferemove
* [fix] Enable yunohost-api systemd service manually
[ kload ]
* [fix] Restart Dovecot at the end of Rspamd configuration script
* [fix] Translate regenconf messages in English and French
-- Jérôme Lebleu <jerome.lebleu@mailoo.org> Sun, 15 Nov 2015 00:23:27 +0100
yunohost (2.3.0) testing; urgency=low
[ breaking changes ]
* Merge all packages into one
* Wheezy compatibility drop
[ features ]
* Implement a regenconf command
* Implement local backup/restore functions
* Allow to filter which app to backup/restore
* Replace the email stack by Rspamd/Rmilter
* Create shallow clone to increase app installation time
* Add helper bash functions for apps developers
* Update app_info to show app installation status
* Implement an app_debug function
* IPv6 compatibility enhancement
[ bugfixes ]
* Display YunoHost packages versions (fix #11)
* Allow empty app arguments in app_install
* Invalidate passwd at user creation/deletion (fix #70)
* Fix skipped_urls for each domain and #68
* Correct logger object in backup_list (fix #75)
* 2nd installation of apps with a hooks directory
* Add netcat-openbsd dependency
* Ensure that arguments are passed to the hook as string
* Use SSL/TLS to fetch app list
* IPv6 record in DynDNS
* Use sudo to execute hook script
* Debian postinst script : only respond to configure
* Handle SSL generation better
* Ensure that the service yunohost-api is always running
* Sieve permission denied
* Do not enable yunohost-firewall service at install
* Open port 1900 when enabling UPnP (fixes #30)
[ other ]
* Add AGPL license
* French translation using Weblate
-- kload <kload@kload.fr> Tue, 03 Nov 2015 11:55:19 +0000
moulinette-yunohost (2.3.1) testing; urgency=low moulinette-yunohost (2.3.1) testing; urgency=low
[ Julien Malik ] [ Julien Malik ]

6
debian/conf/pam/mkhomedir vendored Normal file
View file

@ -0,0 +1,6 @@
Name: Create home directory during login
Default: yes
Priority: 900
Session-Type: Additional
Session:
required pam_mkhomedir.so umask=0022 skel=/etc/skel

29
debian/control vendored
View file

@ -2,14 +2,15 @@ Source: yunohost
Section: utils Section: utils
Priority: extra Priority: extra
Maintainer: YunoHost Contributors <contrib@yunohost.org> Maintainer: YunoHost Contributors <contrib@yunohost.org>
Build-Depends: debhelper (>=9), dh-systemd Build-Depends: debhelper (>=9), dh-systemd, dh-python, python-all (>= 2.7)
Standards-Version: 3.9.6 Standards-Version: 3.9.6
X-Python-Version: >= 2.7
Homepage: https://yunohost.org/ Homepage: https://yunohost.org/
Package: yunohost Package: yunohost
Architecture: all Architecture: all
Depends: ${misc:Depends}, ${shlibs:Depends}, Depends: ${python:Depends}, ${misc:Depends},
moulinette (>= 2.2.1), moulinette (>= 2.3.4),
python-psutil, python-psutil,
python-requests, python-requests,
glances, glances,
@ -27,18 +28,20 @@ Depends: ${misc:Depends}, ${shlibs:Depends},
curl, curl,
mariadb-server | mysql-server, php5-mysql | php5-mysqlnd, mariadb-server | mysql-server, php5-mysql | php5-mysqlnd,
slapd, ldap-utils, sudo-ldap, libnss-ldapd, slapd, ldap-utils, sudo-ldap, libnss-ldapd,
postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, postgrey, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail,
dovecot-ldap, dovecot-lmtpd, dovecot-managesieved, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved,
amavisd-new, razor, pyzor, dovecot-antispam, spamassassin, fail2ban, dovecot-antispam, fail2ban,
nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl, nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl,
dnsmasq, openssl, avahi-daemon, dnsmasq, openssl, avahi-daemon,
ssowat, metronome ssowat, metronome,
rspamd, rmilter (>=1.7.0), redis-server, opendkim-tools
Recommends: yunohost-admin, Recommends: yunohost-admin,
bash-completion, rsyslog, ntp, openssh-server, bash-completion, rsyslog, ntp, openssh-server,
inetutils-ping | iputils-ping,
php5-gd, php5-curl, php-gettext, php5-mcrypt, php5-gd, php5-curl, php-gettext, php5-mcrypt,
udisks-glue, unattended-upgrades, unattended-upgrades,
libdbd-ldap-perl, libnet-dns-perl libdbd-ldap-perl, libnet-dns-perl
Suggests: htop, vim, rsync, acpi-support-base 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,
@ -50,9 +53,11 @@ Replaces: moulinette-yunohost, yunohost-config,
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: YunoHost installation package Description: manageable and configured self-hosting server
YunoHost aims to make self-hosting accessible to everyone. YunoHost aims to make self-hosting accessible to everyone. It configures
an email, Web and IM server alongside a LDAP base. It also provides
facilities to manage users, domains, apps and so.
. .
This package contains YunoHost scripts and binaries to be used by the This package contains YunoHost scripts and binaries to be used by the
moulinette. It allows to manage the server with a command-line tool and moulinette. It allows one to manage the server with a command-line tool
an API. and an API.

5
debian/install vendored
View file

@ -1,8 +1,11 @@
bin/* /usr/bin/ bin/* /usr/bin/
data/bash-completion.d/yunohost /etc/bash_completion.d/
data/actionsmap/* /usr/share/moulinette/actionsmap/ data/actionsmap/* /usr/share/moulinette/actionsmap/
data/hooks/* /usr/share/yunohost/hooks/ data/hooks/* /usr/share/yunohost/hooks/
data/other/* /usr/share/yunohost/yunohost-config/moulinette/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/
data/templates/* /usr/share/yunohost/templates/ data/templates/* /usr/share/yunohost/templates/
data/apps/* /usr/share/yunohost/apps/ data/apps/* /usr/share/yunohost/apps/
lib/yunohost/*.py /usr/lib/moulinette/yunohost/ debian/conf/pam/* /usr/share/pam-configs/
lib/metronome/modules/* /usr/lib/metronome/modules/
locales/* /usr/lib/moulinette/yunohost/locales/ locales/* /usr/lib/moulinette/yunohost/locales/
src/yunohost/*.py /usr/lib/moulinette/yunohost/

8
debian/logrotate vendored Normal file
View file

@ -0,0 +1,8 @@
/var/log/yunohost/*.log {
weekly
rotate 4
delaycompress
compress
notifempty
missingok
}

29
debian/postinst vendored
View file

@ -4,16 +4,25 @@ set -e
do_configure() { do_configure() {
rm -rf /var/cache/moulinette/* rm -rf /var/cache/moulinette/*
service yunohost-api restart
if [ ! -f /etc/yunohost/installed ]; then if [ ! -f /etc/yunohost/installed ]; then
bash /usr/share/yunohost/hooks/conf_regen/01-yunohost bash /usr/share/yunohost/hooks/conf_regen/01-yunohost True
bash /usr/share/yunohost/hooks/conf_regen/02-ssl bash /usr/share/yunohost/hooks/conf_regen/02-ssl True
bash /usr/share/yunohost/hooks/conf_regen/06-slapd bash /usr/share/yunohost/hooks/conf_regen/06-slapd True
bash /usr/share/yunohost/hooks/conf_regen/15-nginx bash /usr/share/yunohost/hooks/conf_regen/15-nginx True
else else
echo "Regenerating configuration, this might take a while..."
yunohost service regenconf yunohost service regenconf
# restart yunohost-firewall if it's running
service yunohost-firewall status > /dev/null \
&& service yunohost-firewall restart \
|| echo "Service yunohost-firewall is not running, you should " \
"consider to start it."
fi fi
# update PAM configs
pam-auth-update --package
} }
# summary of how this script can be called: # summary of how this script can be called:
@ -38,3 +47,13 @@ case "$1" in
exit 1 exit 1
;; ;;
esac esac
# Enable and start yunohost-api service for non-systemd system
if [ -x /etc/init.d/yunohost-api ] && [ ! -d /run/systemd/system ]; then
update-rc.d yunohost-api defaults >/dev/null
invoke-rc.d yunohost-api start || exit $?
fi
#DEBHELPER#
exit 0

11
debian/prerm vendored Normal file
View file

@ -0,0 +1,11 @@
#!/bin/bash
set -e
if [ -x "/etc/init.d/yunohost-firewall" ]; then
invoke-rc.d yunohost-firewall stop || exit $?
fi
#DEBHELPER#
exit 0

12
debian/rules vendored
View file

@ -5,8 +5,14 @@
#export DH_VERBOSE=1 #export DH_VERBOSE=1
%: %:
dh ${@} --with=systemd dh ${@} --with=python2,systemd
override_dh_installinit: override_dh_installinit:
dh_installinit --name=yunohost-api dh_installinit --noscripts
dh_installinit --name=yunohost-firewall
override_dh_systemd_enable:
dh_systemd_enable --name=yunohost-api
dh_systemd_enable --name=yunohost-firewall --no-enable
override_dh_systemd_start:
dh_systemd_start --restart-after-upgrade yunohost-api.service

4
debian/yunohost-api.default vendored Normal file
View file

@ -0,0 +1,4 @@
# Override yunohost-api options.
# Example to log debug: DAEMON_OPTS="--debug"
#
#DAEMON_OPTS=""

View file

@ -21,6 +21,11 @@ PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME SCRIPTNAME=/etc/init.d/$NAME
LOGFILE=/var/log/$NAME.log LOGFILE=/var/log/$NAME.log
# Include yunohost-api defaults if available
if [ -r /etc/default/yunohost-api ]; then
. /etc/default/yunohost-api
fi
# Exit if the package is not installed # Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0 [ -x "$DAEMON" ] || exit 0
@ -45,7 +50,7 @@ do_start()
|| return 1 || return 1
start-stop-daemon --start --background --make-pidfile --quiet --no-close \ start-stop-daemon --start --background --make-pidfile --quiet --no-close \
--pidfile $PIDFILE --exec $DAEMON -- \ --pidfile $PIDFILE --exec $DAEMON -- \
$DAEMON_ARGS >>$LOGFILE 2>&1 \ $DAEMON_OPTS >>$LOGFILE 2>&1 \
|| return 2 || return 2
} }

View file

@ -4,7 +4,9 @@ After=network.target
[Service] [Service]
Type=simple Type=simple
ExecStart=/usr/bin/yunohost-api Environment=DAEMON_OPTS=
EnvironmentFile=-/etc/default/yunohost-api
ExecStart=/usr/bin/yunohost-api $DAEMON_OPTS
ExecReload=/bin/kill -HUP $MAINPID ExecReload=/bin/kill -HUP $MAINPID
Restart=always Restart=always
RestartSec=1 RestartSec=1

11
debian/yunohost-firewall.service vendored Normal file
View file

@ -0,0 +1,11 @@
[Unit]
Description=YunoHost Firewall
Requires=network.target
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/yunohost firewall reload
ExecReload=/usr/bin/yunohost firewall reload
ExecStop=/usr/bin/yunohost firewall stop
RemainAfterExit=yes

View file

@ -0,0 +1,89 @@
-- vim:sts=4 sw=4
-- Metronome IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2012 Rob Hoelz
-- Copyright (C) 2015 YUNOHOST.ORG
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- https://github.com/YunoHost/yunohost-config-metronome/blob/unstable/lib/modules/mod_auth_ldap2.lua
-- adapted to use common LDAP store on Metronome
local ldap = module:require 'ldap';
local new_sasl = require 'util.sasl'.new;
local jsplit = require 'util.jid'.split;
local log = module._log
if not ldap then
return;
end
function new_default_provider(host)
local provider = { name = "ldap2" };
log("debug", "initializing ldap2 authentication provider for host '%s'", host);
function provider.test_password(username, password)
return ldap.bind(username, password);
end
function provider.user_exists(username)
local params = ldap.getparams()
local filter = ldap.filter.combine_and(params.user.filter, params.user.usernamefield .. '=' .. username);
if params.user.usernamefield == 'mail' then
filter = ldap.filter.combine_and(params.user.filter, 'mail=' .. username .. '@*');
end
return ldap.singlematch {
base = params.user.basedn,
filter = filter,
};
end
function provider.get_password(username)
return nil, "Passwords unavailable for LDAP.";
end
function provider.set_password(username, password)
return nil, "Passwords unavailable for LDAP.";
end
function provider.create_user(username, password)
return nil, "Account creation/modification not available with LDAP.";
end
function provider.get_sasl_handler()
local testpass_authentication_profile = {
plain_test = function(sasl, username, password, realm)
return provider.test_password(username, password), true;
end,
order = { "plain_test" },
};
return new_sasl(module.host, testpass_authentication_profile);
end
function provider.is_admin(jid)
local admin_config = ldap.getparams().admin;
if not admin_config then
return;
end
local ld = ldap:getconnection();
local username = jsplit(jid);
local filter = ldap.filter.combine_and(admin_config.filter, admin_config.namefield .. '=' .. username);
return ldap.singlematch {
base = admin_config.basedn,
filter = filter,
};
end
return provider;
end
module:add_item("auth-provider", new_default_provider(module.host));

View file

@ -0,0 +1,243 @@
-- vim:sts=4 sw=4
-- Metronome IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2012 Rob Hoelz
-- Copyright (C) 2015 YUNOHOST.ORG
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
----------------------------------------
-- Constants and such --
----------------------------------------
local setmetatable = setmetatable;
local get_config = require "core.configmanager".get;
local ldap = module:require 'ldap';
local vcardlib = module:require 'vcard';
local st = require 'util.stanza';
local gettime = require 'socket'.gettime;
local log = module._log
if not ldap then
return;
end
local CACHE_EXPIRY = 300;
----------------------------------------
-- Utility Functions --
----------------------------------------
local function ldap_record_to_vcard(record, format)
return vcardlib.create {
record = record,
format = format,
}
end
local get_alias_for_user;
do
local user_cache;
local last_fetch_time;
local function populate_user_cache()
local user_c = get_config(module.host, 'ldap').user;
if not user_c then return; end
local ld = ldap.getconnection();
local usernamefield = user_c.usernamefield;
local namefield = user_c.namefield;
user_cache = {};
for _, attrs in ld:search { base = user_c.basedn, scope = 'onelevel', filter = user_c.filter } do
user_cache[attrs[usernamefield]] = attrs[namefield];
end
last_fetch_time = gettime();
end
function get_alias_for_user(user)
if last_fetch_time and last_fetch_time + CACHE_EXPIRY < gettime() then
user_cache = nil;
end
if not user_cache then
populate_user_cache();
end
return user_cache[user];
end
end
----------------------------------------
-- Base LDAP store class --
----------------------------------------
local function ldap_store(config)
local self = {};
local config = config;
function self:get(username)
return nil, "Data getting is not available for this storage backend";
end
function self:set(username, data)
return nil, "Data setting is not available for this storage backend";
end
return self;
end
local adapters = {};
----------------------------------------
-- Roster Storage Implementation --
----------------------------------------
adapters.roster = function (config)
-- Validate configuration requirements
if not config.groups then return nil; end
local self = ldap_store(config)
function self:get(username)
local ld = ldap.getconnection();
local contacts = {};
local memberfield = config.groups.memberfield;
local namefield = config.groups.namefield;
local filter = memberfield .. '=' .. tostring(username);
local groups = {};
for _, config in ipairs(config.groups) do
groups[ config[namefield] ] = config.name;
end
log("debug", "Found %d group(s) for user %s", select('#', groups), username)
-- XXX this kind of relies on the way we do groups at INOC
for _, attrs in ld:search { base = config.groups.basedn, scope = 'onelevel', filter = filter } do
if groups[ attrs[namefield] ] then
local members = attrs[memberfield];
for _, user in ipairs(members) do
if user ~= username then
local jid = user .. '@' .. module.host;
local record = contacts[jid];
if not record then
record = {
subscription = 'both',
groups = {},
name = get_alias_for_user(user),
};
contacts[jid] = record;
end
record.groups[ groups[ attrs[namefield] ] ] = true;
end
end
end
end
return contacts;
end
function self:set(username, data)
log("warn", "Setting data in Roster LDAP storage is not supported yet")
return nil, "not supported";
end
return self;
end
----------------------------------------
-- vCard Storage Implementation --
----------------------------------------
adapters.vcard = function (config)
-- Validate configuration requirements
if not config.vcard_format or not config.user then return nil; end
local self = ldap_store(config)
function self:get(username)
local ld = ldap.getconnection();
local filter = config.user.usernamefield .. '=' .. tostring(username);
log("debug", "Retrieving vCard for user '%s'", username);
local match = ldap.singlematch {
base = config.user.basedn,
filter = filter,
};
if match then
match.jid = username .. '@' .. module.host
return st.preserialize(ldap_record_to_vcard(match, config.vcard_format));
else
return nil, "username not found";
end
end
function self:set(username, data)
log("warn", "Setting data in vCard LDAP storage is not supported yet")
return nil, "not supported";
end
return self;
end
----------------------------------------
-- Driver Definition --
----------------------------------------
cache = {};
local driver = { name = "ldap" };
function driver:open(store)
log("debug", "Opening ldap storage backend for host '%s' and store '%s'", module.host, store);
if not cache[module.host] then
log("debug", "Caching adapters for the host '%s'", module.host);
local ad_config = get_config(module.host, "ldap");
local ad_cache = {};
for k, v in pairs(adapters) do
ad_cache[k] = v(ad_config);
end
cache[module.host] = ad_cache;
end
local adapter = cache[module.host][store];
if not adapter then
log("info", "Unavailable adapter for store '%s'", store);
return nil, "unsupported-store";
end
return adapter;
end
function driver:stores(username, type, pattern)
return nil, "not implemented";
end
function driver:store_exists(username, datastore, type)
return nil, "not implemented";
end
function driver:purge(username)
return nil, "not implemented";
end
function driver:users()
return nil, "not implemented";
end
module:add_item("data-driver", driver);

View file

@ -1,75 +1,75 @@
{ {
"yunohost_not_installed": "YunoHost ist nicht (oder nicht recht) installiert. Bitte 'yunohost tools postinstall' ablaufen.",
"upgrade_complete": "Upgrade erfolgreich beendet",
"installation_complete": "erfolgreich installiert",
"installation_failed": "Fehler beim Installation",
"app_unknown": "unbekannte App",
"app_no_upgrade": "Keine App zu updaten",
"app_not_installed": "{:s} ist nicht intalliert",
"custom_app_url_required": "Bitte eine URL geben, um deine nüzterspezifische App {:s} zu updaten",
"app_recent_version_required": "{:s} braucht eine jüngstere Fassung von \"moulinette\"",
"app_upgraded": "{:s} erfolgreich updaten",
"app_id_invalid": "Falsche App-ID",
"app_already_installed": "{:s} ist schon installiert", "app_already_installed": "{:s} ist schon installiert",
"app_removed": "{:s} erfolgreich gelöscht", "app_extraction_failed": "Installationsdateien nicht extrahierbar",
"app_id_invalid": "Falsche App-ID",
"app_install_files_invalid": "Ungültige Installationsdateien",
"app_location_already_used": "Eine App ist auf diesem Ort schon installiert", "app_location_already_used": "Eine App ist auf diesem Ort schon installiert",
"app_location_install_failed": "Diese App ist auf diesem Ort nicht Installbar", "app_location_install_failed": "Diese App ist auf diesem Ort nicht Installbar",
"app_extraction_failed": "Installationsdateien nicht extrahierbar", "app_no_upgrade": "Keine App zu updaten",
"app_install_files_invalid": "Ungültige Installationsdateien", "app_not_installed": "{:s} ist nicht intalliert",
"app_recent_version_required": "{:s} braucht eine jüngstere Fassung von \"moulinette\"",
"app_removed": "{:s} erfolgreich gelöscht",
"app_sources_fetch_failed": "Quelledateien nicht abrufbar", "app_sources_fetch_failed": "Quelledateien nicht abrufbar",
"ssowat_conf_updated": "SSOwat beständige Einstellung erfolgreich upgedatet", "app_unknown": "unbekannte App",
"ssowat_conf_generated": "SSOwat-einstellung erfolgreich erzeugt", "app_upgraded": "{:s} erfolgreich updaten",
"mysql_db_creation_failed": "Fehler beim MySQL-datenbankerzeugung", "custom_app_url_required": "Bitte eine URL geben, um deine nüzterspezifische App {:s} zu updaten",
"mysql_db_init_failed": "Fehler beim MySQL-datenbankinitialisierung",
"mysql_db_initialized": "MySQL-datenbank erfolgreich initialisiert",
"extracting": "Extrahierend...",
"downloading": "Herunterladend...",
"done": "Erledigt.",
"domain_unknown": "Unbekannte Domain",
"domain_dyndns_invalid": "Domain mit DynDNS nicht nützbar",
"domain_dyndns_already_subscribed": "Du hast dich schon für einen DynDNS-domain angemeldet",
"domain_dyndns_root_unknown": "Unbekannte DynDNS-hauptdomain",
"domain_cert_gen_failed": "Zertifizierung nicht erzeugbar", "domain_cert_gen_failed": "Zertifizierung nicht erzeugbar",
"domain_exists": "diese Domain existiert schon",
"domain_creation_failed": "Domain nicht erzeugbar",
"domain_created": "Domain erfolgreich erzeugt", "domain_created": "Domain erfolgreich erzeugt",
"domain_uninstall_app_first": "Mindestens eine App ist schon auf diese Domain installiert. Bitte erst die Apps deinstallieren, und nur dann die Domain löschen.", "domain_creation_failed": "Domain nicht erzeugbar",
"domain_deletion_failed": "Domain nicht löschbar",
"domain_deleted": "Domain erfolgreich gelöscht", "domain_deleted": "Domain erfolgreich gelöscht",
"no_internet_connection": "Server not connected to the Internet", "domain_deletion_failed": "Domain nicht löschbar",
"dyndns_key_generating": "DNS key is being generated, it may take a while...", "domain_dyndns_already_subscribed": "Du hast dich schon für einen DynDNS-domain angemeldet",
"dyndns_unavailable": "DynDNS-subdomain nicht verfügbar", "domain_dyndns_invalid": "Domain mit DynDNS nicht nützbar",
"dyndns_registration_failed": "DynDNS-domain {:s} nicht registrierbar", "domain_dyndns_root_unknown": "Unbekannte DynDNS-hauptdomain",
"dyndns_registered": "DynDNS-domain erfolgreich registriert", "domain_exists": "diese Domain existiert schon",
"dyndns_ip_update_failed": "IP-adress auf DynDNS nicht updatbar", "domain_uninstall_app_first": "Mindestens eine App ist schon auf diese Domain installiert. Bitte erst die Apps deinstallieren, und nur dann die Domain löschen.",
"dyndns_ip_updated": "IP-adress auf DynDNS erfolgreich upgedatet", "domain_unknown": "Unbekannte Domain",
"done": "Erledigt.",
"downloading": "Herunterladend...",
"dyndns_cron_installed": "DynDNS cron job erfolgreich installiert", "dyndns_cron_installed": "DynDNS cron job erfolgreich installiert",
"dyndns_cron_remove_failed": "DynDNS cron job nicht löschbar", "dyndns_cron_remove_failed": "DynDNS cron job nicht löschbar",
"dyndns_cron_removed": "DynDNS cron job erfolgreich gelöscht", "dyndns_cron_removed": "DynDNS cron job erfolgreich gelöscht",
"iptables_unavailable": "Du kannst nicht hier die IP-Tabelle bearbeiten. Entweder bist du in einen Container oder deinen Systemkern erhält es nicht.", "dyndns_ip_update_failed": "IP-adress auf DynDNS nicht updatbar",
"dyndns_ip_updated": "IP-adress auf DynDNS erfolgreich upgedatet",
"dyndns_key_generating": "DNS key is being generated, it may take a while...",
"dyndns_registered": "DynDNS-domain erfolgreich registriert",
"dyndns_registration_failed": "DynDNS-domain {:s} nicht registrierbar",
"dyndns_unavailable": "DynDNS-subdomain nicht verfügbar",
"extracting": "Extrahierend...",
"firewall_reloaded": "Firewall erfolgreich neu geladen", "firewall_reloaded": "Firewall erfolgreich neu geladen",
"hook_choice_invalid": "ungültige Wahl '{:s}'",
"hook_argument_missing": "Fehlend Argument '{:s}'", "hook_argument_missing": "Fehlend Argument '{:s}'",
"mountpoint_unknown": "unbekannten Einhängepunkt", "hook_choice_invalid": "ungültige Wahl '{:s}'",
"unit_unknown": "unbekannte Einheit '{:s}'", "installation_complete": "erfolgreich installiert",
"monitor_period_invalid": "Falschen Zeitraum", "installation_failed": "Fehler beim Installation",
"monitor_stats_no_update": "Keine Monitoringstatistik zu updaten", "iptables_unavailable": "Du kannst nicht hier die IP-Tabelle bearbeiten. Entweder bist du in einen Container oder deinen Systemkern erhält es nicht.",
"monitor_stats_file_not_found": "Statistikdatei nicht gefunden",
"monitor_stats_period_unavailable": "Keine verfügbare Statistik für diese Zeitraum",
"monitor_enabled": "Servermonitoring erfolgreich aktiviert",
"monitor_disabled": "Servermonitoring erfolgreich deaktiviert", "monitor_disabled": "Servermonitoring erfolgreich deaktiviert",
"monitor_not_enabled": "Servermonitoring ist nicht aktiviert", "monitor_enabled": "Servermonitoring erfolgreich aktiviert",
"monitor_glances_con_failed": "Verbindung mit Glances-server nicht möglich", "monitor_glances_con_failed": "Verbindung mit Glances-server nicht möglich",
"service_unknown": "Unbekannte Dienst '{:s}'", "monitor_not_enabled": "Servermonitoring ist nicht aktiviert",
"service_start_failed": "Kann nicht '{:s}' -dienst starten", "monitor_period_invalid": "Falschen Zeitraum",
"monitor_stats_file_not_found": "Statistikdatei nicht gefunden",
"monitor_stats_no_update": "Keine Monitoringstatistik zu updaten",
"monitor_stats_period_unavailable": "Keine verfügbare Statistik für diese Zeitraum",
"mountpoint_unknown": "unbekannten Einhängepunkt",
"mysql_db_creation_failed": "Fehler beim MySQL-datenbankerzeugung",
"mysql_db_init_failed": "Fehler beim MySQL-datenbankinitialisierung",
"mysql_db_initialized": "MySQL-datenbank erfolgreich initialisiert",
"no_internet_connection": "Server not connected to the Internet",
"service_already_started": "'{:s}' -dienst ist schon im Betrieb", "service_already_started": "'{:s}' -dienst ist schon im Betrieb",
"service_started": "'{:s}' -dienst erfolgreich gestartet",
"service_stop_failed": "Kann nicht '{:s}' -dienst stoppen",
"service_already_stopped": "'{:s}' -dienst ist schon abgestoppt", "service_already_stopped": "'{:s}' -dienst ist schon abgestoppt",
"service_stopped": "'{:s}' -dienst erfolgreich abgestoppt",
"service_enable_failed": "Kann nicht '{:s}' -dienst aktivieren",
"service_enabled": "'{:s}' -dienst erfolgreich aktiviert",
"service_disable_failed": "Kann nicht '{:s}' -dienst deaktivieren", "service_disable_failed": "Kann nicht '{:s}' -dienst deaktivieren",
"service_disabled": "'{:s}' -dienst erfolgreich deaktiviert", "service_disabled": "'{:s}' -dienst erfolgreich deaktiviert",
"service_status_failed": "Kann nicht '{:s}' -dienststatus feststellen" "service_enable_failed": "Kann nicht '{:s}' -dienst aktivieren",
"service_enabled": "'{:s}' -dienst erfolgreich aktiviert",
"service_start_failed": "Kann nicht '{:s}' -dienst starten",
"service_started": "'{:s}' -dienst erfolgreich gestartet",
"service_status_failed": "Kann nicht '{:s}' -dienststatus feststellen",
"service_stop_failed": "Kann nicht '{:s}' -dienst stoppen",
"service_stopped": "'{:s}' -dienst erfolgreich abgestoppt",
"service_unknown": "Unbekannte Dienst '{:s}'",
"ssowat_conf_generated": "SSOwat-einstellung erfolgreich erzeugt",
"ssowat_conf_updated": "SSOwat beständige Einstellung erfolgreich upgedatet",
"unit_unknown": "unbekannte Einheit '{:s}'",
"upgrade_complete": "Upgrade erfolgreich beendet",
"yunohost_not_installed": "YunoHost ist nicht (oder nicht recht) installiert. Bitte 'yunohost tools postinstall' ablaufen."
} }

View file

@ -16,19 +16,23 @@
"appslist_removed" : "Apps list successfully removed", "appslist_removed" : "Apps list successfully removed",
"app_unknown" : "Unknown app", "app_unknown" : "Unknown app",
"app_no_upgrade" : "No app to upgrade", "app_no_upgrade" : "No app to upgrade",
"app_not_installed" : "{:s} is not installed", "app_not_installed" : "{app:s} is not installed",
"app_not_correctly_installed" : "{app:s} seems to be not correctly installed",
"custom_app_url_required" : "You must provide an URL to upgrade your custom app {:s}", "custom_app_url_required" : "You must provide an URL to upgrade your custom app {:s}",
"app_recent_version_required" : "{:s} requires a more recent version of the moulinette", "app_recent_version_required" : "{app:s} requires a more recent version of YunoHost",
"app_upgraded" : "{:s} successfully upgraded", "app_upgraded" : "{app:s} successfully upgraded",
"app_upgrade_failed" : "Unable to upgrade all apps", "app_upgrade_failed" : "Unable to upgrade all apps",
"app_id_invalid" : "Invalid app id", "app_id_invalid" : "Invalid app id",
"app_already_installed" : "{:s} is already installed", "app_already_installed" : "{:s} is already installed",
"app_removed" : "{: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",
"app_extraction_failed" : "Unable to extract installation files", "app_extraction_failed" : "Unable to extract installation files",
"app_install_files_invalid" : "Invalid installation files", "app_install_files_invalid" : "Invalid installation files",
"app_manifest_invalid" : "Invalid app manifest", "app_manifest_invalid" : "Invalid app manifest",
"app_argument_choice_invalid" : "Invalid choice for argument '{name:s}', it must be one of {choices:s}",
"app_argument_invalid" : "Invalid value for argument '{name:s}': {error:s}",
"app_argument_required" : "Argument '{name:s}' is required",
"app_sources_fetch_failed" : "Unable to fetch sources files", "app_sources_fetch_failed" : "Unable to fetch sources files",
"ssowat_conf_updated" : "SSOwat persistent configuration successfully updated", "ssowat_conf_updated" : "SSOwat persistent configuration successfully updated",
"ssowat_conf_generated" : "SSOwat configuration successfully generated", "ssowat_conf_generated" : "SSOwat configuration successfully generated",
@ -37,7 +41,8 @@
"mysql_db_initialized" : "MySQL database successfully initialized", "mysql_db_initialized" : "MySQL database successfully initialized",
"extracting" : "Extracting...", "extracting" : "Extracting...",
"downloading" : "Downloading...", "downloading" : "Downloading...",
"executing_script": "Executing script...", "executing_script": "Executing script '{script:s}'...",
"executing_command": "Executing command '{command:s}'...",
"done" : "Done.", "done" : "Done.",
"path_removal_failed" : "Unable to remove path {:s}", "path_removal_failed" : "Unable to remove path {:s}",
@ -84,8 +89,8 @@
"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 '{:s}'",
"hook_choice_invalid" : "Invalid choice '{:s}'", "hook_exec_failed" : "Script execution failed",
"hook_argument_missing" : "Missing argument '{:s}'", "hook_exec_not_terminated" : "Script execution hasnt terminated",
"mountpoint_unknown" : "Unknown mountpoint", "mountpoint_unknown" : "Unknown mountpoint",
"unit_unknown" : "Unknown unit '{:s}'", "unit_unknown" : "Unknown unit '{:s}'",
@ -116,6 +121,11 @@
"service_status_failed" : "Unable to determine status of service '{:s}'", "service_status_failed" : "Unable to determine status of service '{:s}'",
"service_no_log" : "No log to display for service '{:s}'", "service_no_log" : "No log to display for service '{:s}'",
"service_cmd_exec_failed" : "Unable to execute command '{:s}'", "service_cmd_exec_failed" : "Unable to execute command '{:s}'",
"services_configured": "Configuration successfully generated",
"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}",
"show_diff": "Here are the differences:\n{diff:s}",
"network_check_smtp_ok" : "Outbound mail (SMTP port 25) is not blocked", "network_check_smtp_ok" : "Outbound mail (SMTP port 25) is not blocked",
"network_check_smtp_ko" : "Outbound mail (SMTP port 25) seems to be blocked by your network", "network_check_smtp_ko" : "Outbound mail (SMTP port 25) seems to be blocked by your network",
@ -143,31 +153,36 @@
"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",
"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_running_hooks" : "Running backup hooks...", "backup_running_hooks" : "Running backup hooks...",
"backup_running_app_script" : "Running backup script of app '{:s}'...", "backup_running_app_script" : "Running backup script of app '{app:s}'...",
"backup_creating_archive" : "Creating the backup archive...", "backup_creating_archive" : "Creating the backup archive...",
"backup_extracting_archive" : "Extracting the backup archive...", "backup_extracting_archive" : "Extracting the backup archive...",
"backup_archive_open_failed" : "Unable to open the backup archive", "backup_archive_open_failed" : "Unable to open the backup archive",
"backup_archive_name_unknown" : "Unknown local backup archive named '{:s}'", "backup_archive_name_unknown" : "Unknown local backup archive named '{name:s}'",
"backup_archive_name_exists" : "Backup archive name already exists", "backup_archive_name_exists" : "Backup archive name already exists",
"backup_archive_hook_not_exec" : "Hook '{hook:s}' not executed in this backup",
"backup_archive_app_not_found" : "App '{app:s}' not found in the backup archive",
"backup_app_failed" : "Unable to back up the app '{app:s}'", "backup_app_failed" : "Unable to back up the app '{app:s}'",
"backup_nothings_done" : "There is nothing to save", "backup_nothings_done" : "There is nothing to save",
"backup_cleaning_failed" : "Unable to clean backup directory", "backup_cleaning_failed" : "Unable to clean backup temporary directory",
"backup_complete" : "Backup complete", "backup_complete" : "Backup complete",
"backup_invalid_archive" : "Invalid backup archive", "backup_invalid_archive" : "Invalid backup archive",
"backup_hook_unavailable" : "The hook '{:s}' is not in this backup", "restore_action_required" : "You must specify something to restore",
"restore_confirm_yunohost_installed" : "Do you really want to restore an already installed system? [{answers:s}]", "restore_confirm_yunohost_installed" : "Do you really want to restore an already installed system? [{answers:s}]",
"restore_hook_unavailable" : "Restauration hook '{hook:s}' not available on your system",
"restore_app_failed" : "Unable to restore the app '{app:s}'", "restore_app_failed" : "Unable to restore the app '{app:s}'",
"restore_running_hooks" : "Running restoration hooks...", "restore_running_hooks" : "Running restoration hooks...",
"restore_running_app_script" : "Running restore script of app '{app:s}'...",
"restore_failed" : "Unable to restore the system", "restore_failed" : "Unable to restore the system",
"restore_nothings_done" : "Nothing has been restored",
"restore_cleaning_failed" : "Unable to clean restoration temporary directory",
"restore_complete" : "Restore complete", "restore_complete" : "Restore complete",
"restore_already_installed_app": "An app is already installed with the id '{app:s}'", "restore_already_installed_app": "An app is already installed with the id '{app:s}'",
"unbackup_app" : "App '{:s}' will not be saved", "unbackup_app" : "App '{app:s}' will not be saved",
"unrestorable_app" : "App '{:s}' will not be restored",
"restore_app_copy_failed" : "Unable to copy the restore script of app '{app:s}'",
"no_restore_script": "No restore script found for the app '{app:s}'", "no_restore_script": "No restore script found for the app '{app:s}'",
"unrestore_app" : "App '{:s}' will not be restored", "unrestore_app" : "App '{app:s}' will not be restored",
"backup_delete_error" : "Unable to delete '{:s}'", "backup_delete_error" : "Unable to delete '{path:s}'",
"backup_deleted" : "Backup successfully deleted", "backup_deleted" : "Backup successfully deleted",
"field_invalid" : "Invalid field '{:s}'", "field_invalid" : "Invalid field '{:s}'",

View file

@ -1,176 +1,176 @@
{ {
"yunohost_not_installed": "YunoHost no está instalado o la instilación ha cumplido con errores. Por favor, ejecute 'yunohost tools postinstall'.",
"upgrade_complete": "La actualización se ha completado",
"installation_complete": "La instalación se ha completado",
"installation_failed": "La Instalación se ha fracasado",
"unexpected_error": "Un error ha ocurrido",
"action_invalid": "Acción inválida '{:s}'", "action_invalid": "Acción inválida '{:s}'",
"license_undefined": "indefinido", "admin_password": "Contraseña administrativa",
"no_appslist_found": "No se encontró ninguna lista de Apps", "admin_password_change_failed": "No se pudo cambiar la contraseña",
"custom_appslist_name_required": "Debe proporcionar un nombre para la lista de aplicaciones personalizadas ", "admin_password_changed": "Contraseña administrativa se cambió con éxito",
"appslist_retrieve_error": "No se pudo recuperar la lista de aplicaciones a distancia ",
"appslist_fetched": "Lista de aplicaciones se trajo con éxito",
"appslist_unknown": "Lista de aplicaciones desconocidas",
"appslist_removed": "Lista de aplicaciones se eliminó con éxito",
"app_unknown": "App desconocida",
"app_no_upgrade": "Ninguna app a actualizar",
"app_not_installed": "{:s} no está instalado.",
"custom_app_url_required": " Debe proporcionar una URL para actualizar su aplicación personalizada {:s} ",
"app_recent_version_required": "{:s} requiere una versión más reciente de moulinette ",
"app_upgraded": "{:s} actualizado con éxito",
"app_upgrade_failed": "No se pudo actualizar todas las aplicaciones ",
"app_id_invalid": "id de la aplicación inválida ",
"app_already_installed": "{:s} ya está instalado ", "app_already_installed": "{:s} ya está instalado ",
"app_removed": "{:s} era eliminado con éxito ", "app_extraction_failed": "No se pudo extraer los archivos de instalación ",
"app_id_invalid": "id de la aplicación inválida ",
"app_install_files_invalid": "Archivos de instalación inválidos ",
"app_location_already_used": "Una aplicación ya está instalado en este lugar", "app_location_already_used": "Una aplicación ya está instalado en este lugar",
"app_location_install_failed": "No se pudo instalar la aplicación en esta lugar", "app_location_install_failed": "No se pudo instalar la aplicación en esta lugar",
"app_extraction_failed": "No se pudo extraer los archivos de instalación ",
"app_install_files_invalid": "Archivos de instalación inválidos ",
"app_manifest_invalid": "Manifesto de la aplicación es inválido", "app_manifest_invalid": "Manifesto de la aplicación es inválido",
"app_no_upgrade": "Ninguna app a actualizar",
"app_not_installed": "{:s} no está instalado.",
"app_recent_version_required": "{:s} requiere una versión más reciente de moulinette ",
"app_removed": "{:s} era eliminado con éxito ",
"app_sources_fetch_failed": "No se pudo descargar los archivos de códigos fuentes", "app_sources_fetch_failed": "No se pudo descargar los archivos de códigos fuentes",
"ssowat_conf_updated": "Configuración persistente SSOwat actualizada con éxito", "app_unknown": "App desconocida",
"ssowat_conf_generated": "Configuración SSOwat generado con éxito ", "app_upgrade_failed": "No se pudo actualizar todas las aplicaciones ",
"mysql_db_creation_failed": "No se pudo crear el base de datos MySQL", "app_upgraded": "{:s} actualizado con éxito",
"mysql_db_init_failed": "No se pudo inicializar el base de datos MySQL.", "appslist_fetched": "Lista de aplicaciones se trajo con éxito",
"mysql_db_initialized": "Base de datos MySQL inicializado con éxito", "appslist_removed": "Lista de aplicaciones se eliminó con éxito",
"extracting": "Extrayendo...", "appslist_retrieve_error": "No se pudo recuperar la lista de aplicaciones a distancia ",
"downloading": "Descargando...", "appslist_unknown": "Lista de aplicaciones desconocidas",
"executing_script": "Ejecutando script...", "ask_current_admin_password": "Contraseña administrativa presente",
"done": "Completo.", "ask_email": "Correo electrónico",
"path_removal_failed": "No se pudo quitar la ruta {:s}", "ask_firstname": "Nombre",
"domain_unknown": "Dominio desconocido", "ask_lastname": "Apellido",
"domain_dyndns_invalid": "El dominio no es valido para usar con DynDNS", "ask_list_to_remove": "Lista a quitar",
"domain_dyndns_already_subscribed": "Ya te has suscrito a un dominio DynDNS.", "ask_main_domain": "Dominio principal",
"domain_dyndns_root_unknown": "Dominio raíz DynDNS desconocido ", "ask_new_admin_password": "Contraseña administrativa nueva",
"domain_cert_gen_failed": "No se pudo crear certificado", "ask_password": "Contraseña",
"domain_exists": "El dominio ya existe", "backup_archive_name_exists": "Un archivo ya existe con el nombre del archivo de backup",
"backup_archive_name_unknown": "El nombre archivo local de backup está desconocido",
"backup_archive_open_failed": "No se pudo abrir el archivo backup",
"backup_complete": "El backup se ha completado",
"backup_creating_archive": "Creando el archivo backup...",
"backup_extracting_archive": "Extrayendo el archivo backup...",
"backup_invalid_archive": "Archivo de backup es inválido",
"backup_output_directory_forbidden": "Carpeta de salida prohibida",
"backup_output_directory_not_empty": "La carpeta de salida no está vacía",
"backup_output_directory_required": "Debe proporcionar un directorio de salida para el backup",
"backup_running_hooks": "Ejecutando los hooks de backup...",
"custom_app_url_required": " Debe proporcionar una URL para actualizar su aplicación personalizada {:s} ",
"custom_appslist_name_required": "Debe proporcionar un nombre para la lista de aplicaciones personalizadas ",
"dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, por favor, ejecuta 'apt-get remove bind9 && apt-get install dnsmasq'", "dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, por favor, ejecuta 'apt-get remove bind9 && apt-get install dnsmasq'",
"domain_cert_gen_failed": "No se pudo crear certificado",
"domain_created": "Dominio creado con éxito.",
"domain_creation_failed": "No se pudo crear el dominio",
"domain_deleted": "Dominio borrado con éxito.",
"domain_deletion_failed": "No se pudo borrar el dominio.",
"domain_dyndns_already_subscribed": "Ya te has suscrito a un dominio DynDNS.",
"domain_dyndns_invalid": "El dominio no es valido para usar con DynDNS",
"domain_dyndns_root_unknown": "Dominio raíz DynDNS desconocido ",
"domain_exists": "El dominio ya existe",
"domain_uninstall_app_first": "Uno o más apps están instalados en este dominio. Por favor, desinstalarlos antes de quitar este dominio.",
"domain_unknown": "Dominio desconocido",
"domain_zone_exists": "El archivo de zonas DNS ya existe.", "domain_zone_exists": "El archivo de zonas DNS ya existe.",
"domain_zone_not_found": "Archivo de zonas DNS por el dominio [:s] no estaba encontrado", "domain_zone_not_found": "Archivo de zonas DNS por el dominio [:s] no estaba encontrado",
"domain_creation_failed": "No se pudo crear el dominio", "done": "Completo.",
"domain_created": "Dominio creado con éxito.", "downloading": "Descargando...",
"domain_uninstall_app_first": "Uno o más apps están instalados en este dominio. Por favor, desinstalarlos antes de quitar este dominio.",
"domain_deletion_failed": "No se pudo borrar el dominio.",
"domain_deleted": "Dominio borrado con éxito.",
"no_internet_connection": "El servidor no está conectado al Internet.",
"dyndns_key_generating": "Generación del llave de DNS está en curso. Este podría durar unos momentos...",
"dyndns_unavailable": "Subdominio DynDNS no disponible",
"dyndns_registration_failed": "No se pudo registrar el dominio DynDNS: {:s}",
"dyndns_registered": "El dominio DynDNS era registrado con éxito.",
"dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en DynDNS",
"dyndns_ip_updated": "La dirección IP era actualizado en DynDNS con éxito",
"dyndns_cron_installed": "El trabajo cron de DynDNS se ha instalado con éxito", "dyndns_cron_installed": "El trabajo cron de DynDNS se ha instalado con éxito",
"dyndns_cron_remove_failed": "No se pudo quitar el trabajo cron DynDNS", "dyndns_cron_remove_failed": "No se pudo quitar el trabajo cron DynDNS",
"dyndns_cron_removed": "Trabajo cron DynDNS se quitó con éxito", "dyndns_cron_removed": "Trabajo cron DynDNS se quitó con éxito",
"port_available": "El puerto {} está disponible", "dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en DynDNS",
"port_unavailable": "El puerto {} no está disponible", "dyndns_ip_updated": "La dirección IP era actualizado en DynDNS con éxito",
"port_already_opened": "El puerto {} ya está abierto por {:s} connecciones", "dyndns_key_generating": "Generación del llave de DNS está en curso. Este podría durar unos momentos...",
"port_already_closed": "El puerto {} ya está cerrado por {:s} connecciones.", "dyndns_registered": "El dominio DynDNS era registrado con éxito.",
"iptables_unavailable": "No puedes modificar los iptables aquí. Eres en un contenedor o su kernel no soporte este opción.", "dyndns_registration_failed": "No se pudo registrar el dominio DynDNS: {:s}",
"ip6tables_unavailable": "No puedes modificar los ip6tables aquí. Eres en un contenedor o su kernel no soporte este opción.", "dyndns_unavailable": "Subdominio DynDNS no disponible",
"upnp_dev_not_found": "No se encontró ninguno dispositivo UPnP ", "executing_script": "Ejecutando script...",
"upnp_port_open_failed": "No se pudo abrir puertos por UPnP", "extracting": "Extrayendo...",
"upnp_enabled": "UPnP activado con éxito", "field_invalid": "Campo inválido '{:s}'",
"upnp_disabled": "UPnP deshabilitado con éxito",
"firewall_rules_cmd_failed": "Algunos reglas del cortafuegos han fracasado. Para más información, vea al archivo historial.",
"firewall_reload_failed": "No se pudo recargar el cortafuegos", "firewall_reload_failed": "No se pudo recargar el cortafuegos",
"firewall_reloaded": "Cortafuegos recargado con éxito", "firewall_reloaded": "Cortafuegos recargado con éxito",
"firewall_rules_cmd_failed": "Algunos reglas del cortafuegos han fracasado. Para más información, vea al archivo historial.",
"hook_argument_missing": "Falta un parámetro '{:s}'",
"hook_choice_invalid": "Selección inválida '{:s}'",
"hook_list_by_invalid": "La propiedad de este hook es inválida", "hook_list_by_invalid": "La propiedad de este hook es inválida",
"hook_name_unknown": "Hook desconocido '{:s}'", "hook_name_unknown": "Hook desconocido '{:s}'",
"hook_choice_invalid": "Selección inválida '{:s}'", "installation_complete": "La instalación se ha completado",
"hook_argument_missing": "Falta un parámetro '{:s}'", "installation_failed": "La Instalación se ha fracasado",
"mountpoint_unknown": "Punto de montaje desconocido", "ip6tables_unavailable": "No puedes modificar los ip6tables aquí. Eres en un contenedor o su kernel no soporte este opción.",
"unit_unknown": "Unidad '{:s}' desconocido", "iptables_unavailable": "No puedes modificar los iptables aquí. Eres en un contenedor o su kernel no soporte este opción.",
"monitor_period_invalid": "Período de tiempo inválido", "ldap_initialized": "LDAP se inició con éxito",
"monitor_stats_no_update": "No hay ninguna estadísticos de la supervisión del sistema a realizar", "license_undefined": "indefinido",
"monitor_stats_file_not_found": "No se pudo encontrar el archivo de estadísticos", "mail_alias_remove_failed": "No se pudo quitar el alias de correos '{:s}'",
"monitor_stats_period_unavailable": "No hay estadísticos del período del tiempo", "mail_domain_unknown": "El dominio de correos '{:s}' es desconocido",
"monitor_enabled": "Supervisión del sistema activado con éxito", "mail_forward_remove_failed": "No se pudo quitar la reenvía de correos '{:s}'",
"maindomain_change_failed": "No se pudo cambiar el dominio principal",
"maindomain_changed": "Dominio principal se cambió con éxito",
"monitor_disabled": "Supervisión del sistema era desactivado con éxito", "monitor_disabled": "Supervisión del sistema era desactivado con éxito",
"monitor_not_enabled": "Supervisión del sistema no está activado", "monitor_enabled": "Supervisión del sistema activado con éxito",
"monitor_glances_con_failed": "No se pudo conectar al servidor de Glances", "monitor_glances_con_failed": "No se pudo conectar al servidor de Glances",
"service_unknown": "Servicio desconocido '{:s}'", "monitor_not_enabled": "Supervisión del sistema no está activado",
"monitor_period_invalid": "Período de tiempo inválido",
"monitor_stats_file_not_found": "No se pudo encontrar el archivo de estadísticos",
"monitor_stats_no_update": "No hay ninguna estadísticos de la supervisión del sistema a realizar",
"monitor_stats_period_unavailable": "No hay estadísticos del período del tiempo",
"mountpoint_unknown": "Punto de montaje desconocido",
"mysql_db_creation_failed": "No se pudo crear el base de datos MySQL",
"mysql_db_init_failed": "No se pudo inicializar el base de datos MySQL.",
"mysql_db_initialized": "Base de datos MySQL inicializado con éxito",
"new_domain_required": "Debe proporcionar el dominio principal nuevo",
"no_appslist_found": "No se encontró ninguna lista de Apps",
"no_internet_connection": "El servidor no está conectado al Internet.",
"packages_no_upgrade": "No hay actualización por ningun paquete",
"packages_upgrade_critical_later": "Los paquetes críticos ({:s}) se actualizarán más tarde",
"packages_upgrade_failed": "No se pudo actualizar todo de los paquetes",
"path_removal_failed": "No se pudo quitar la ruta {:s}",
"pattern_backup_archive_name": "Debe que ser un nombre de archivo válido con los caracteres alfanumericos, o los -_.",
"pattern_domain": "El nombre de dominio debe ser válido (e.g. mi-dominio.org)",
"pattern_email": "Debe ser una direccion de email válido (e.g. alguien@dominio.org)",
"pattern_firstname": "Debe ser un nombre válido",
"pattern_lastname": "Debe ser un apellido válido",
"pattern_listname": "Los caracteres deben ser alfanuméricos o el guion bajo.",
"pattern_password": "Debe ser a menos de 3 caracteres",
"pattern_port": "El numéro del puerto debe ser válido (i.e. 0-65535)",
"pattern_port_or_range": "El numéro del puerto debe ser válido (i.e. 0-65535) o un intervalo de puertos (e.g. 100:200)",
"pattern_username": "Debe contener solamente caracteres alfanuméricos o la guion bajo",
"port_already_closed": "El puerto {} ya está cerrado por {:s} connecciones.",
"port_already_opened": "El puerto {} ya está abierto por {:s} connecciones",
"port_available": "El puerto {} está disponible",
"port_unavailable": "El puerto {} no está disponible",
"restore_complete": "Restauración se ha completado",
"restore_confirm_yunohost_installed": "Estás seguro que quieres restaurar a un sistema que ya está instalado? [{answers:s}]",
"restore_failed": "No se pudo restaurar el sistema",
"restore_running_hooks": "Ejecutando hooks de restauración...",
"service_add_failed": "No se pudo añadir el servicio '{:s}'", "service_add_failed": "No se pudo añadir el servicio '{:s}'",
"service_added": "Servicio añadido con éxito", "service_added": "Servicio añadido con éxito",
"service_already_started": "El servicio '{:s}' ya se ha empezado",
"service_already_stopped": "El servicio '{:s}' ya está parado ",
"service_cmd_exec_failed": "No se pudo ejecutar comando '{:s}'",
"service_disable_failed": "No se pudo desactivar el servicio '{:s}'",
"service_disabled": "Servicio '{:s}' desactivado con éxito",
"service_enable_failed": "No se pudo activar el servicio '{:s}'",
"service_enabled": "Servicio '{:s}' activado con éxito",
"service_no_log": "No hay archivo historial del servicio '{:s}' a exhibir",
"service_remove_failed": "No se pudo quitar el servicio '{:s}'", "service_remove_failed": "No se pudo quitar el servicio '{:s}'",
"service_removed": "Servicio quitado con éxito", "service_removed": "Servicio quitado con éxito",
"service_start_failed": "No se pudo empezar el servicio '{:s}'", "service_start_failed": "No se pudo empezar el servicio '{:s}'",
"service_already_started": "El servicio '{:s}' ya se ha empezado",
"service_started": "El servicio '{:s}' se empezó con éxito", "service_started": "El servicio '{:s}' se empezó con éxito",
"service_stop_failed": "No se pudo parar el servicio '{:s}'",
"service_already_stopped": "El servicio '{:s}' ya está parado ",
"service_stopped": "Servicio '{:s}' parado con éxito",
"service_enable_failed": "No se pudo activar el servicio '{:s}'",
"service_enabled": "Servicio '{:s}' activado con éxito",
"service_disable_failed": "No se pudo desactivar el servicio '{:s}'",
"service_disabled": "Servicio '{:s}' desactivado con éxito",
"service_status_failed": "No se pudo discernir el estado del servicio '{:s}'", "service_status_failed": "No se pudo discernir el estado del servicio '{:s}'",
"service_no_log": "No hay archivo historial del servicio '{:s}' a exhibir", "service_stop_failed": "No se pudo parar el servicio '{:s}'",
"service_cmd_exec_failed": "No se pudo ejecutar comando '{:s}'", "service_stopped": "Servicio '{:s}' parado con éxito",
"ldap_initialized": "LDAP se inició con éxito", "service_unknown": "Servicio desconocido '{:s}'",
"admin_password_change_failed": "No se pudo cambiar la contraseña", "ssowat_conf_generated": "Configuración SSOwat generado con éxito ",
"admin_password_changed": "Contraseña administrativa se cambió con éxito", "ssowat_conf_updated": "Configuración persistente SSOwat actualizada con éxito",
"new_domain_required": "Debe proporcionar el dominio principal nuevo", "system_upgraded": "Actualización del sistema se ha completado con éxito.",
"maindomain_change_failed": "No se pudo cambiar el dominio principal", "system_username_exists": "Nombre de usuario ya existe en los usuarios del sistema",
"maindomain_changed": "Dominio principal se cambió con éxito", "unbackup_app": "La App '{:s}' no será guardada",
"yunohost_installing": "Instalando YunoHost...", "unexpected_error": "Un error ha ocurrido",
"unit_unknown": "Unidad '{:s}' desconocido",
"unrestore_app": "La App '{:s}' no será restaurada",
"update_cache_failed": "No se pudo actualizar el cache APT",
"updating_apt_cache": "Actualizando la lista de paquetes disponibles...",
"upgrade_complete": "La actualización se ha completado",
"upgrading_packages": "Actualizando paquetes...",
"upnp_dev_not_found": "No se encontró ninguno dispositivo UPnP ",
"upnp_disabled": "UPnP deshabilitado con éxito",
"upnp_enabled": "UPnP activado con éxito",
"upnp_port_open_failed": "No se pudo abrir puertos por UPnP",
"user_created": "Usuario creado con éxito",
"user_creation_failed": "No se pudo crear un usuario nuevo",
"user_deleted": "Usuario creado con éxito",
"user_deletion_failed": "No se pudo quitar el usuario",
"user_info_failed": "No se pudo traer la información del usuario. ",
"user_unknown": "usuario desconocido",
"user_update_failed": "No se pudo actualizar el usuario",
"user_updated": "Usuario actualizado con éxito",
"yunohost_already_installed": "YunoHost ya está instalado", "yunohost_already_installed": "YunoHost ya está instalado",
"yunohost_ca_creation_failed": "No se pudo crear un autoridad de certificación nuevo", "yunohost_ca_creation_failed": "No se pudo crear un autoridad de certificación nuevo",
"yunohost_configured": "YunoHost se configuró con éxito.", "yunohost_configured": "YunoHost se configuró con éxito.",
"updating_apt_cache": "Actualizando la lista de paquetes disponibles...", "yunohost_installing": "Instalando YunoHost...",
"update_cache_failed": "No se pudo actualizar el cache APT", "yunohost_not_installed": "YunoHost no está instalado o la instilación ha cumplido con errores. Por favor, ejecute 'yunohost tools postinstall'."
"packages_no_upgrade": "No hay actualización por ningun paquete",
"packages_upgrade_critical_later": "Los paquetes críticos ({:s}) se actualizarán más tarde",
"upgrading_packages": "Actualizando paquetes...",
"packages_upgrade_failed": "No se pudo actualizar todo de los paquetes",
"system_upgraded": "Actualización del sistema se ha completado con éxito.",
"backup_output_directory_required": "Debe proporcionar un directorio de salida para el backup",
"backup_output_directory_forbidden": "Carpeta de salida prohibida",
"backup_output_directory_not_empty": "La carpeta de salida no está vacía",
"backup_running_hooks": "Ejecutando los hooks de backup...",
"backup_creating_archive": "Creando el archivo backup...",
"backup_extracting_archive": "Extrayendo el archivo backup...",
"backup_archive_open_failed": "No se pudo abrir el archivo backup",
"backup_archive_name_unknown": "El nombre archivo local de backup está desconocido",
"backup_archive_name_exists": "Un archivo ya existe con el nombre del archivo de backup",
"backup_complete": "El backup se ha completado",
"backup_invalid_archive": "Archivo de backup es inválido",
"restore_confirm_yunohost_installed": "Estás seguro que quieres restaurar a un sistema que ya está instalado? [{answers:s}]",
"restore_running_hooks": "Ejecutando hooks de restauración...",
"restore_failed": "No se pudo restaurar el sistema",
"restore_complete": "Restauración se ha completado",
"unbackup_app": "La App '{:s}' no será guardada",
"unrestore_app": "La App '{:s}' no será restaurada",
"field_invalid": "Campo inválido '{:s}'",
"mail_domain_unknown": "El dominio de correos '{:s}' es desconocido",
"mail_alias_remove_failed": "No se pudo quitar el alias de correos '{:s}'",
"mail_forward_remove_failed": "No se pudo quitar la reenvía de correos '{:s}'",
"user_unknown": "usuario desconocido",
"system_username_exists": "Nombre de usuario ya existe en los usuarios del sistema",
"user_creation_failed": "No se pudo crear un usuario nuevo",
"user_created": "Usuario creado con éxito",
"user_deletion_failed": "No se pudo quitar el usuario",
"user_deleted": "Usuario creado con éxito",
"user_update_failed": "No se pudo actualizar el usuario",
"user_updated": "Usuario actualizado con éxito",
"user_info_failed": "No se pudo traer la información del usuario. ",
"admin_password": "Contraseña administrativa",
"ask_firstname": "Nombre",
"ask_lastname": "Apellido",
"ask_email": "Correo electrónico",
"ask_password": "Contraseña",
"ask_current_admin_password": "Contraseña administrativa presente",
"ask_new_admin_password": "Contraseña administrativa nueva",
"ask_main_domain": "Dominio principal",
"ask_list_to_remove": "Lista a quitar",
"pattern_username": "Debe contener solamente caracteres alfanuméricos o la guion bajo",
"pattern_firstname": "Debe ser un nombre válido",
"pattern_lastname": "Debe ser un apellido válido",
"pattern_email": "Debe ser una direccion de email válido (e.g. alguien@dominio.org)",
"pattern_password": "Debe ser a menos de 3 caracteres",
"pattern_domain": "El nombre de dominio debe ser válido (e.g. mi-dominio.org)",
"pattern_listname": "Los caracteres deben ser alfanuméricos o el guion bajo.",
"pattern_port": "El numéro del puerto debe ser válido (i.e. 0-65535)",
"pattern_port_or_range": "El numéro del puerto debe ser válido (i.e. 0-65535) o un intervalo de puertos (e.g. 100:200)",
"pattern_backup_archive_name": "Debe que ser un nombre de archivo válido con los caracteres alfanumericos, o los -_."
} }

View file

@ -1,178 +1,185 @@
{ {
"yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'.",
"upgrade_complete": "Mise à jour terminée",
"installation_complete": "Installation terminée",
"installation_failed": "Échec de l'installation",
"unexpected_error": "Une erreur inattendue est survenue",
"action_invalid": "Action '{:s}' incorrecte", "action_invalid": "Action '{:s}' incorrecte",
"license_undefined": "indéfinie", "admin_password": "Mot de passe d'administration",
"no_appslist_found": "Aucune liste d'applications trouvée", "admin_password_change_failed": "Impossible de modifier le mot de passe d'administration",
"custom_appslist_name_required": "Vous devez spécifier un nom pour votre liste d'applications personnalisée", "admin_password_changed": "Mot de passe d'administration modifié avec succès",
"appslist_retrieve_error": "Impossible de récupérer la liste d'applications distante",
"appslist_fetched": "Liste d'applications récupérée avec succès",
"appslist_unknown": "Liste d'applications inconnue",
"appslist_removed": "Liste d'applications supprimée avec succès",
"app_unknown": "Application inconnue",
"app_no_upgrade": "Aucune application à mettre à jour",
"app_not_installed": "{:s} n'est pas installé",
"custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application locale {:s}",
"app_recent_version_required": "{:s} nécessite une version plus récente de la moulinette",
"app_upgraded": "{:s} mis à jour avec succès",
"app_upgrade_failed": "Impossible de mettre à jour toutes les applications",
"app_id_invalid": "Id d'application incorrect",
"app_already_installed": "{:s} est déjà installé", "app_already_installed": "{:s} est déjà installé",
"app_removed": "{:s} supprimé avec succès", "app_extraction_failed": "Impossible d'extraire les fichiers d'installation",
"app_id_invalid": "Id d'application incorrect",
"app_install_files_invalid": "Fichiers d'installation incorrects",
"app_location_already_used": "Une application est déjà installée à cet emplacement", "app_location_already_used": "Une application est déjà installée à cet emplacement",
"app_location_install_failed": "Impossible d'installer l'application à cet emplacement", "app_location_install_failed": "Impossible d'installer l'application à cet emplacement",
"app_extraction_failed": "Impossible d'extraire les fichiers d'installation",
"app_install_files_invalid": "Fichiers d'installation incorrects",
"app_manifest_invalid": "Manifeste d'application incorrect", "app_manifest_invalid": "Manifeste d'application incorrect",
"app_no_upgrade": "Aucune application à mettre à jour",
"app_not_installed": "{:s} n'est pas installé",
"app_recent_version_required": "{:s} nécessite une version plus récente de la moulinette",
"app_removed": "{:s} supprimé avec succès",
"app_sources_fetch_failed": "Impossible de récupérer les fichiers sources", "app_sources_fetch_failed": "Impossible de récupérer les fichiers sources",
"ssowat_conf_updated": "Configuration persistante de SSOwat mise à jour avec succès", "app_unknown": "Application inconnue",
"ssowat_conf_generated": "Configuration de SSOwat générée avec succès", "app_upgrade_failed": "Impossible de mettre à jour toutes les applications",
"mysql_db_creation_failed": "Impossible de créer la base de donnée MySQL", "app_upgraded": "{:s} mis à jour avec succès",
"mysql_db_init_failed": "Impossible d'initialiser la base de donnée MySQL", "appslist_fetched": "Liste d'applications récupérée avec succès",
"mysql_db_initialized": "Base de donnée MySQL initialisée avec succès", "appslist_removed": "Liste d'applications supprimée avec succès",
"extracting": "Extraction...", "appslist_retrieve_error": "Impossible de récupérer la liste d'applications distante",
"downloading": "Téléchargement...", "appslist_unknown": "Liste d'applications inconnue",
"executing_script": "Exécution du script...", "ask_current_admin_password": "Mot de passe d'administration actuel",
"done": "Terminé.", "ask_email": "Adresse mail",
"path_removal_failed": "Impossible de supprimer le chemin {:s}", "ask_firstname": "Prénom",
"domain_unknown": "Domaine inconnu", "ask_lastname": "Nom",
"domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS", "ask_list_to_remove": "Liste à supprimer",
"domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS", "ask_main_domain": "Domaine principal",
"domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu", "ask_new_admin_password": "Nouveau mot de passe d'administration",
"domain_cert_gen_failed": "Impossible de générer le certificat", "ask_password": "Mot de passe",
"domain_exists": "Le domaine existe déjà", "backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà",
"backup_archive_name_unknown": "Nom d'archive de sauvegarde locale inconnu",
"backup_archive_open_failed": "Impossible d'ouvrir l'archive de sauvegarde",
"backup_complete": "Sauvegarde terminée",
"backup_creating_archive": "Création de l'archive de sauvegarde...",
"backup_extracting_archive": "Extraction de l'archive de sauvegarde...",
"backup_hook_unknown": "Script de sauvegarde '{hook:s}' inconnu",
"backup_invalid_archive": "Archive de sauvegarde incorrecte",
"backup_output_directory_forbidden": "Dossier de sortie interdit",
"backup_output_directory_not_empty": "Le dossier de sortie n'est pas vide",
"backup_output_directory_required": "Vous devez spécifier un dossier de sortie pour la sauvegarde",
"backup_running_app_script": "Lancement du script de sauvegarde de l'application '{:s}'...",
"backup_running_hooks": "Exécution des scripts de sauvegarde...",
"custom_app_url_required": "Vous devez spécifier une URL pour mettre à jour votre application locale {:s}",
"custom_appslist_name_required": "Vous devez spécifier un nom pour votre liste d'applications personnalisée",
"dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer 'apt-get remove bind9 && apt-get install dnsmasq'", "dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer 'apt-get remove bind9 && apt-get install dnsmasq'",
"domain_cert_gen_failed": "Impossible de générer le certificat",
"domain_created": "Domaine créé avec succès",
"domain_creation_failed": "Impossible de créer le domaine",
"domain_deleted": "Domaine supprimé avec succès",
"domain_deletion_failed": "Impossible de supprimer le domaine",
"domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS",
"domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS",
"domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu",
"domain_exists": "Le domaine existe déjà",
"domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d'abord les désinstaller avant de supprimer ce domaine.",
"domain_unknown": "Domaine inconnu",
"domain_zone_exists": "Le fichier de zone DNS existe déjà", "domain_zone_exists": "Le fichier de zone DNS existe déjà",
"domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}", "domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}",
"domain_creation_failed": "Impossible de créer le domaine", "done": "Terminé.",
"domain_created": "Domaine créé avec succès", "downloading": "Téléchargement...",
"domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d'abord les désinstaller avant de supprimer ce domaine.",
"domain_deletion_failed": "Impossible de supprimer le domaine",
"domain_deleted": "Domaine supprimé avec succès",
"no_internet_connection": "Le serveur n'est pas connecté à Internet",
"no_ipv6_connectivity": "IPv6 n'est pas disponible",
"dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre du temps...",
"dyndns_unavailable": "Sous-domaine DynDNS indisponible",
"dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {:s}",
"dyndns_registered": "Domaine DynDNS enregistré avec succès",
"dyndns_ip_update_failed": "Impossible de mettre à jour l'adresse IP sur le domaine DynDNS",
"dyndns_ip_updated": "Adresse IP mise à jour avec succès sur le domaine DynDNS",
"dyndns_cron_installed": "Tâche cron pour DynDNS installée avec succès", "dyndns_cron_installed": "Tâche cron pour DynDNS installée avec succès",
"dyndns_cron_remove_failed": "Impossible d'enlever la tâche cron pour DynDNS", "dyndns_cron_remove_failed": "Impossible d'enlever la tâche cron pour DynDNS",
"dyndns_cron_removed": "Tâche cron pour DynDNS enlevée avec succès", "dyndns_cron_removed": "Tâche cron pour DynDNS enlevée avec succès",
"port_available": "Le port {} est disponible", "dyndns_ip_update_failed": "Impossible de mettre à jour l'adresse IP sur le domaine DynDNS",
"port_unavailable": "Le port {} n'est pas disponible", "dyndns_ip_updated": "Adresse IP mise à jour avec succès sur le domaine DynDNS",
"port_already_opened": "Le port {} est déjà ouvert pour les connexions {:s}", "dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre du temps...",
"port_already_closed": "Le port {} est déjà fermé pour les connexions {:s}", "dyndns_registered": "Domaine DynDNS enregistré avec succès",
"iptables_unavailable": "Vous ne pouvez pas faire joujou avec iptables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", "dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {:s}",
"ip6tables_unavailable": "Vous ne pouvez pas faire joujou avec ip6tables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.", "dyndns_unavailable": "Sous-domaine DynDNS indisponible",
"upnp_dev_not_found": "Aucun périphérique compatible UPnP trouvé", "executing_script": "Exécution du script...",
"upnp_port_open_failed": "Impossible d'ouvrir les ports avec UPnP", "extracting": "Extraction...",
"upnp_enabled": "UPnP activé avec succès", "field_invalid": "Champ incorrect : {:s}",
"upnp_disabled": "UPnP désactivé avec succès",
"firewall_rules_cmd_failed": "Certaines règles du pare-feu n'ont pas pu être appliquées. Pour plus d'informations, consultez le journal.",
"firewall_reload_failed": "Impossible de recharger le pare-feu", "firewall_reload_failed": "Impossible de recharger le pare-feu",
"firewall_reloaded": "Pare-feu rechargé avec succès", "firewall_reloaded": "Pare-feu rechargé avec succès",
"firewall_rules_cmd_failed": "Certaines règles du pare-feu n'ont pas pu être appliquées. Pour plus d'informations, consultez le journal.",
"format_datetime_short": "%d/%m/%Y %H:%M",
"hook_argument_missing": "Argument manquant : '{:s}'",
"hook_choice_invalid": "Choix incorrect : '{:s}'",
"hook_list_by_invalid": "Propriété pour lister les scripts incorrecte", "hook_list_by_invalid": "Propriété pour lister les scripts incorrecte",
"hook_name_unknown": "Nom de script '{:s}' inconnu", "hook_name_unknown": "Nom de script '{:s}' inconnu",
"hook_choice_invalid": "Choix incorrect : '{:s}'", "installation_complete": "Installation terminée",
"hook_argument_missing": "Argument manquant : '{:s}'", "installation_failed": "Échec de l'installation",
"mountpoint_unknown": "Point de montage inconnu", "ip6tables_unavailable": "Vous ne pouvez pas faire joujou avec ip6tables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.",
"unit_unknown": "Unité '{:s}' inconnue", "iptables_unavailable": "Vous ne pouvez pas faire joujou avec iptables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.",
"monitor_period_invalid": "Période de temps incorrect", "ldap_initialized": "Répertoire LDAP initialisé avec succès",
"monitor_stats_no_update": "Aucune donnée de l'état du serveur à mettre à jour", "license_undefined": "indéfinie",
"monitor_stats_file_not_found": "Fichier de données de l'état du serveur introuvable", "mail_alias_remove_failed": "Impossible de supprimer l'adresse mail supplémentaire '{:s}'",
"monitor_stats_period_unavailable": "Aucune donnée de l'état du serveur disponible pour la période", "mail_domain_unknown": "Domaine '{:s}' de l'adresse mail inconnu",
"monitor_enabled": "Suivi de l'état du serveur activé avec succès", "mail_forward_remove_failed": "Impossible de supprimer l'adresse mail de transfert '{:s}'",
"maindomain_change_failed": "Impossible de modifier le domaine principal",
"maindomain_changed": "Domaine principal modifié avec succès",
"monitor_disabled": "Suivi de l'état du serveur désactivé avec succès", "monitor_disabled": "Suivi de l'état du serveur désactivé avec succès",
"monitor_not_enabled": "Le suivi de l'état du serveur n'est pas activé", "monitor_enabled": "Suivi de l'état du serveur activé avec succès",
"monitor_glances_con_failed": "Impossible de se connecter au serveur Glances", "monitor_glances_con_failed": "Impossible de se connecter au serveur Glances",
"service_unknown": "Service '{:s}' inconnu", "monitor_not_enabled": "Le suivi de l'état du serveur n'est pas activé",
"monitor_period_invalid": "Période de temps incorrect",
"monitor_stats_file_not_found": "Fichier de données de l'état du serveur introuvable",
"monitor_stats_no_update": "Aucune donnée de l'état du serveur à mettre à jour",
"monitor_stats_period_unavailable": "Aucune donnée de l'état du serveur disponible pour la période",
"mountpoint_unknown": "Point de montage inconnu",
"mysql_db_creation_failed": "Impossible de créer la base de donnée MySQL",
"mysql_db_init_failed": "Impossible d'initialiser la base de donnée MySQL",
"mysql_db_initialized": "Base de donnée MySQL initialisée avec succès",
"new_domain_required": "Vous devez spécifier le nouveau domaine principal",
"no_appslist_found": "Aucune liste d'applications trouvée",
"no_internet_connection": "Le serveur n'est pas connecté à Internet",
"no_ipv6_connectivity": "IPv6 n'est pas disponible",
"no_such_conf_file": "Le fichier {file:s} nexiste pas, il ne peut pas être copié",
"packages_no_upgrade": "Il n'y a aucun paquet à mettre à jour",
"packages_upgrade_critical_later": "Les paquets critiques ({:s}) seront mis à jour plus tard",
"packages_upgrade_failed": "Impossible de mettre à jour tous les paquets",
"path_removal_failed": "Impossible de supprimer le chemin {:s}",
"pattern_backup_archive_name": "Doit être un nom de fichier valide composé de caractères alphanumérique et -_. uniquement",
"pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.org)",
"pattern_email": "Doit être une adresse mail valide (ex. : someone@domain.org)",
"pattern_firstname": "Doit être un prénom valide",
"pattern_lastname": "Doit être un nom valide",
"pattern_listname": "Doit être composé uniquement de caractères alphanumérique et de tiret bas",
"pattern_password": "Doit être composé d'au moins 3 caractères",
"pattern_port": "Doit être un numéro de port valide (0-65535)",
"pattern_port_or_range": "Doit être un numéro de port valide (0-65535) ou une gamme de ports (ex : 100:200)",
"pattern_username": "Doit être composé uniquement de caractères alphanumérique minuscule et de tiret bas",
"port_already_closed": "Le port {} est déjà fermé pour les connexions {:s}",
"port_already_opened": "Le port {} est déjà ouvert pour les connexions {:s}",
"port_available": "Le port {} est disponible",
"port_unavailable": "Le port {} n'est pas disponible",
"restore_complete": "Restauration terminée",
"restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]",
"restore_failed": "Impossible de restaurer le système",
"restore_running_app_script": "Lancement du script de restauration pour l'application '{app:s}'...",
"restore_running_hooks": "Exécution des scripts de restauration...",
"service_add_configuration": "Ajout du fichier de configuration {file:s}",
"service_add_failed": "Impossible d'ajouter le service '{:s}'", "service_add_failed": "Impossible d'ajouter le service '{:s}'",
"service_added": "Service ajouté avec succès", "service_added": "Service ajouté avec succès",
"service_already_started": "Le service '{:s}' est déjà démarré",
"service_already_stopped": "Le service '{:s}' est déjà arrêté",
"service_cmd_exec_failed": "Impossible d'exécuter la commande '{:s}'",
"service_configuration_conflict": "Le fichier {file:s} a été modifié depuis sa dernière génération. Veuillez y appliquer les modifications manuellement ou utiliser loption --force (ce qui écrasera toutes les modifications effectuées sur le fichier). Voici les différences:\n{diff:s}",
"service_disable_failed": "Impossible de désactiver le service '{:s}'",
"service_disabled": "Service '{:s}' désactivé avec succès",
"service_enable_failed": "Impossible d'activer le service '{:s}'",
"service_enabled": "Service '{:s}' activé avec succès",
"service_no_log": "Aucun journal a afficher pour le service '{:s}'",
"service_remove_failed": "Impossible d'enlever le service '{:s}'", "service_remove_failed": "Impossible d'enlever le service '{:s}'",
"service_removed": "Service enlevé avec succès", "service_removed": "Service enlevé avec succès",
"service_start_failed": "Impossible de démarrer le service '{:s}'", "service_start_failed": "Impossible de démarrer le service '{:s}'",
"service_already_started": "Le service '{:s}' est déjà démarré",
"service_started": "Service '{:s}' démarré avec succès", "service_started": "Service '{:s}' démarré avec succès",
"service_stop_failed": "Impossible d'arrêter le service '{:s}'",
"service_already_stopped": "Le service '{:s}' est déjà arrêté",
"service_stopped": "Service '{:s}' arrêté avec succès",
"service_enable_failed": "Impossible d'activer le service '{:s}'",
"service_enabled": "Service '{:s}' activé avec succès",
"service_disable_failed": "Impossible de désactiver le service '{:s}'",
"service_disabled": "Service '{:s}' désactivé avec succès",
"service_status_failed": "Impossible de déterminer le statut du service '{:s}'", "service_status_failed": "Impossible de déterminer le statut du service '{:s}'",
"service_no_log": "Aucun journal a afficher pour le service '{:s}'", "service_stop_failed": "Impossible d'arrêter le service '{:s}'",
"service_cmd_exec_failed": "Impossible d'exécuter la commande '{:s}'", "service_stopped": "Service '{:s}' arrêté avec succès",
"ldap_initialized": "Répertoire LDAP initialisé avec succès", "service_unknown": "Service '{:s}' inconnu",
"admin_password_change_failed": "Impossible de modifier le mot de passe d'administration", "services_configured": "La configuration a été générée avec succès",
"admin_password_changed": "Mot de passe d'administration modifié avec succès", "ssowat_conf_generated": "Configuration de SSOwat générée avec succès",
"new_domain_required": "Vous devez spécifier le nouveau domaine principal", "ssowat_conf_updated": "Configuration persistante de SSOwat mise à jour avec succès",
"maindomain_change_failed": "Impossible de modifier le domaine principal", "system_upgraded": "Système mis à jour avec succès",
"maindomain_changed": "Domaine principal modifié avec succès", "system_username_exists": "Le nom d'utilisateur existe déjà dans les utilisateurs système",
"yunohost_installing": "Installation de YunoHost...", "unbackup_app": "L'application '{:s}' ne sera pas sauvegardée",
"unexpected_error": "Une erreur inattendue est survenue",
"unit_unknown": "Unité '{:s}' inconnue",
"unrestore_app": "L'application '{app:s}' ne sera pas restaurée",
"update_cache_failed": "Impossible de mettre à jour le cache de l'APT",
"updating_apt_cache": "Mise à jour de la liste des paquets disponibles...",
"upgrade_complete": "Mise à jour terminée",
"upgrading_packages": "Mise à jour des paquets...",
"upnp_dev_not_found": "Aucun périphérique compatible UPnP trouvé",
"upnp_disabled": "UPnP désactivé avec succès",
"upnp_enabled": "UPnP activé avec succès",
"upnp_port_open_failed": "Impossible d'ouvrir les ports avec UPnP",
"user_created": "Utilisateur créé avec succès",
"user_creation_failed": "Impossible de créer l'utilisateur",
"user_deleted": "Utilisateur supprimé avec succès",
"user_deletion_failed": "Impossible de supprimer l'utilisateur",
"user_info_failed": "Impossible de récupérer les informations de l'utilisateur",
"user_unknown": "Utilisateur inconnu",
"user_update_failed": "Impossible de modifier l'utilisateur",
"user_updated": "Utilisateur modifié avec succès",
"yunohost_already_installed": "YunoHost est déjà installé", "yunohost_already_installed": "YunoHost est déjà installé",
"yunohost_ca_creation_failed": "Impossible de créer l'autorité de certification", "yunohost_ca_creation_failed": "Impossible de créer l'autorité de certification",
"yunohost_configured": "YunoHost configuré avec succès", "yunohost_configured": "YunoHost configuré avec succès",
"updating_apt_cache": "Mise à jour de la liste des paquets disponibles...", "yunohost_installing": "Installation de YunoHost...",
"update_cache_failed": "Impossible de mettre à jour le cache de l'APT", "yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'."
"packages_no_upgrade": "Il n'y a aucun paquet à mettre à jour",
"packages_upgrade_critical_later": "Les paquets critiques ({:s}) seront mis à jour plus tard",
"upgrading_packages": "Mise à jour des paquets...",
"packages_upgrade_failed": "Impossible de mettre à jour tous les paquets",
"system_upgraded": "Système mis à jour avec succès",
"backup_output_directory_required": "Vous devez spécifier un dossier de sortie pour la sauvegarde",
"backup_output_directory_forbidden": "Dossier de sortie interdit",
"backup_output_directory_not_empty": "Le dossier de sortie n'est pas vide",
"backup_running_hooks": "Exécution des scripts de sauvegarde...",
"backup_creating_archive": "Création de l'archive de sauvegarde...",
"backup_extracting_archive": "Extraction de l'archive de sauvegarde...",
"backup_archive_open_failed": "Impossible d'ouvrir l'archive de sauvegarde",
"backup_archive_name_unknown": "Nom d'archive de sauvegarde locale inconnu",
"backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà",
"backup_complete": "Sauvegarde terminée",
"backup_invalid_archive": "Archive de sauvegarde incorrecte",
"restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]",
"restore_running_hooks": "Exécution des scripts de restauration...",
"restore_failed": "Impossible de restaurer le système",
"restore_complete": "Restauration terminée",
"unbackup_app": "L'application '{:s}' ne sera pas sauvegardée",
"unrestore_app": "L'application '{:s}' ne sera pas restaurée",
"field_invalid": "Champ incorrect : {:s}",
"mail_domain_unknown": "Domaine '{:s}' de l'adresse mail inconnu",
"mail_alias_remove_failed": "Impossible de supprimer l'adresse mail supplémentaire '{:s}'",
"mail_forward_remove_failed": "Impossible de supprimer l'adresse mail de transfert '{:s}'",
"system_username_exists": "Le nom d'utilisateur existe déjà dans les utilisateurs système",
"user_unknown": "Utilisateur inconnu",
"user_creation_failed": "Impossible de créer l'utilisateur",
"user_created": "Utilisateur créé avec succès",
"user_deletion_failed": "Impossible de supprimer l'utilisateur",
"user_deleted": "Utilisateur supprimé avec succès",
"user_update_failed": "Impossible de modifier l'utilisateur",
"user_updated": "Utilisateur modifié avec succès",
"user_info_failed": "Impossible de récupérer les informations de l'utilisateur",
"admin_password": "Mot de passe d'administration",
"ask_firstname": "Prénom",
"ask_lastname": "Nom",
"ask_email": "Adresse mail",
"ask_password": "Mot de passe",
"ask_current_admin_password": "Mot de passe d'administration actuel",
"ask_new_admin_password": "Nouveau mot de passe d'administration",
"ask_main_domain": "Domaine principal",
"ask_list_to_remove": "Liste à supprimer",
"pattern_username": "Doit être composé uniquement de caractères alphanumérique minuscule et de tiret bas",
"pattern_firstname": "Doit être un prénom valide",
"pattern_lastname": "Doit être un nom valide",
"pattern_email": "Doit être une adresse mail valide (ex. : someone@domain.org)",
"pattern_password": "Doit être composé d'au moins 3 caractères",
"pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.org)",
"pattern_listname": "Doit être composé uniquement de caractères alphanumérique et de tiret bas",
"pattern_port": "Doit être un numéro de port valide (0-65535)",
"pattern_port_or_range": "Doit être un numéro de port valide (0-65535) ou une gamme de ports (ex : 100:200)",
"pattern_backup_archive_name": "Doit être un nom de fichier valide composé de caractères alphanumérique et -_. uniquement",
"format_datetime_short": "%d/%m/%Y %H:%M"
} }

1
locales/it.json Normal file
View file

@ -0,0 +1 @@
{}

66
locales/nl.json Normal file
View file

@ -0,0 +1,66 @@
{
"action_invalid": "Ongeldige actie '{:s}'",
"admin_password": "Administration password",
"app_already_installed": "{:s} is al geïnstalleerd",
"app_extraction_failed": "Kan installatiebestanden niet uitpakken",
"app_id_invalid": "Ongeldige app-id",
"app_install_files_invalid": "Ongeldige installatiebestanden",
"app_location_already_used": "Er is al een app geïnstalleerd op deze locatie",
"app_location_install_failed": "Kan app niet installeren op deze locatie",
"app_manifest_invalid": "Ongeldig app-manifest",
"app_no_upgrade": "Geen apps op te upgraden",
"app_not_installed": "{:s} is niet geinstalleerd ",
"app_recent_version_required": "{:s} vereist een nieuwere versie van moulinette",
"app_removed": "{:s} succesvol verwijderd",
"app_sources_fetch_failed": "Kan bronbestanden niet ophalen",
"app_unknown": "Onbekende app",
"app_upgrade_failed": "Kan niet alle apps updaten",
"app_upgraded": "{:s} succesvol geüpgrade ",
"appslist_fetched": "App-lijst succesvol aangemaakt.",
"appslist_removed": "App-lijst succesvol verwijderd",
"appslist_unknown": "Onbekende app-lijst",
"ask_current_admin_password": "Huidig administratorwachtwoord",
"ask_email": "Email-adres",
"ask_firstname": "Voornaam",
"ask_lastname": "Achternaam",
"ask_new_admin_password": "Nieuw administratorwachtwoord",
"ask_password": "Wachtwoord",
"custom_app_url_required": "U moet een URL opgeven om uw aangepaste app {:s} bij te werken",
"custom_appslist_name_required": "U moet een naam opgeven voor uw aangepaste app-lijst",
"dnsmasq_isnt_installed": "dnsmasq lijkt niet geïnstalleerd te zijn, voer alstublieft het volgende commando uit: 'apt-get remove bind9 && apt-get install dnsmasq'",
"domain_cert_gen_failed": "Kan certificaat niet genereren",
"domain_created": "Domein succesvol aangemaakt",
"domain_creation_failed": "Kan domein niet aanmaken",
"domain_deleted": "Domein succesvol verwijderd",
"domain_deletion_failed": "Kan domein niet verwijderen",
"domain_dyndns_root_unknown": "Onbekend DynDNS root domein",
"domain_exists": "Domein bestaat al",
"domain_uninstall_app_first": "Een of meerdere apps zijn geïnstalleerd op dit domein, verwijder deze voordat u het domein verwijderd.",
"domain_unknown": "Onbekend domein",
"domain_zone_exists": "DNS zone bestand bestaat al",
"domain_zone_not_found": "DNS zone bestand niet gevonden voor domein: {:s}",
"done": "Voltooid.",
"downloading": "Downloaden...",
"dyndns_key_generating": "DNS sleutel word aangemaakt, wacht een moment...",
"executing_script": "Script uitvoeren...",
"extracting": "Uitpakken...",
"installation_complete": "Installatie voltooid",
"installation_failed": "Installatie gefaald",
"license_undefined": "undefined",
"mysql_db_creation_failed": "Aanmaken MySQL database gefaald",
"mysql_db_init_failed": "Initialiseren MySQL database gefaald",
"mysql_db_initialized": "MySQL database succesvol geïnitialiseerd",
"no_appslist_found": "Geen app-lijsten gevonden",
"no_internet_connection": "Server is niet verbonden met het internet",
"path_removal_failed": "Kan pad niet verwijderen {:s}",
"port_already_closed": "Poort {} is al gesloten voor {:s} verbindingen",
"port_already_opened": "Poort {} is al open voor {:s} verbindingen",
"port_available": "Poort {} is beschikbaar",
"port_unavailable": "Poort {} is niet beschikbaar",
"unexpected_error": "Er is een onbekende fout opgetreden",
"upgrade_complete": "Upgrade voltooid",
"upnp_dev_not_found": "Geen UPnP apparaten gevonden",
"upnp_disabled": "UPnP successvol uitgeschakeld",
"upnp_enabled": "UPnP succesvol ingeschakeld",
"upnp_port_open_failed": "Kan UPnP poorten niet openen"
}

View file

@ -1,143 +1,143 @@
{ {
"yunohost_not_installed": "YunoHost ainda não está corretamente configurado. Por favor execute as 'ferramentas pós-instalação yunohost'",
"upgrade_complete": "Atualização completa",
"installation_complete": "Instalação concluída",
"installation_failed": "A instalação falhou",
"unexpected_error": "Ocorreu um erro inesperado",
"action_invalid": "Invalid action '{:s}'", "action_invalid": "Invalid action '{:s}'",
"license_undefined": "indefinido", "admin_password": "Senha de administração",
"no_appslist_found": "Não foi encontrada a lista de aplicações", "admin_password_change_failed": "Não foi possível alterar a senha",
"custom_appslist_name_required": "Deve fornecer um nome para a sua lista de aplicações personalizada", "admin_password_changed": "Senha de administração alterada com êxito",
"appslist_retrieve_error": "Não foi possível obter a lista de aplicações remotas",
"appslist_fetched": "Lista de aplicações processada com êxito",
"appslist_unknown": "Lista de aplicaçoes desconhecida",
"appslist_removed": "Lista de aplicações removida com êxito",
"app_unknown": "Aplicação desconhecida",
"app_no_upgrade": "Não existem aplicações para atualizar",
"app_not_installed": "{:s} não está instalada",
"custom_app_url_required": "Deve proporcionar uma URL para atualizar a sua aplicação personalizada {:s}",
"app_recent_version_required": "{:s} requer uma versão mais recente da moulinette",
"app_upgraded": "{:s} atualizada com êxito",
"app_upgrade_failed": "Unable to upgrade all apps",
"app_id_invalid": "ID da aplicação invélida",
"app_already_installed": "{:s} já está instalada", "app_already_installed": "{:s} já está instalada",
"app_removed": "{:s} removida com êxito", "app_extraction_failed": "Não foi possível extrair os ficheiros para instalação",
"app_id_invalid": "ID da aplicação invélida",
"app_install_files_invalid": "Ficheiros para instalação corrompidos",
"app_location_already_used": "Já existe uma aplicação instalada neste diretório", "app_location_already_used": "Já existe uma aplicação instalada neste diretório",
"app_location_install_failed": "Não foi possível instalar a aplicação neste diretório", "app_location_install_failed": "Não foi possível instalar a aplicação neste diretório",
"app_extraction_failed": "Não foi possível extrair os ficheiros para instalação",
"app_install_files_invalid": "Ficheiros para instalação corrompidos",
"app_manifest_invalid": "Manifesto da aplicação inválido", "app_manifest_invalid": "Manifesto da aplicação inválido",
"app_no_upgrade": "Não existem aplicações para atualizar",
"app_not_installed": "{:s} não está instalada",
"app_recent_version_required": "{:s} requer uma versão mais recente da moulinette",
"app_removed": "{:s} removida com êxito",
"app_sources_fetch_failed": "Impossível obter os códigos fontes", "app_sources_fetch_failed": "Impossível obter os códigos fontes",
"ssowat_conf_updated": "Configuração persistente SSOwat atualizada com êxito", "app_unknown": "Aplicação desconhecida",
"ssowat_conf_generated": "Configuração SSOwat gerada com êxito", "app_upgrade_failed": "Unable to upgrade all apps",
"mysql_db_creation_failed": "Criação da base de dados MySQL falhou", "app_upgraded": "{:s} atualizada com êxito",
"mysql_db_init_failed": "Inicialização da base de dados MySQL falhou", "appslist_fetched": "Lista de aplicações processada com êxito",
"mysql_db_initialized": "Base de dados MySQL iniciada com êxito", "appslist_removed": "Lista de aplicações removida com êxito",
"extracting": "Extração em curso...", "appslist_retrieve_error": "Não foi possível obter a lista de aplicações remotas",
"downloading": "Transferência em curso...", "appslist_unknown": "Lista de aplicaçoes desconhecida",
"executing_script": "A executar o script...", "ask_current_admin_password": "Senha de administração atual",
"done": "Concluído.", "ask_email": "Correio eletrónico",
"path_removal_failed": "Incapaz remover o caminho {:s}", "ask_firstname": "Primeiro nome",
"domain_unknown": "Domínio desconhecido", "ask_lastname": "Último nome",
"domain_dyndns_invalid": "Domínio inválido para ser utilizado com DynDNS", "ask_list_to_remove": "Lista para remover",
"domain_dyndns_already_subscribed": "Já subscreveu um domínio DynDNS", "ask_main_domain": "Domínio principal",
"domain_dyndns_root_unknown": "Domínio root (administrador) DynDNS desconhecido", "ask_new_admin_password": "Senha de administração nova",
"ask_password": "Senha",
"custom_app_url_required": "Deve proporcionar uma URL para atualizar a sua aplicação personalizada {:s}",
"custom_appslist_name_required": "Deve fornecer um nome para a sua lista de aplicações personalizada",
"domain_cert_gen_failed": "Não foi possível gerar o certificado", "domain_cert_gen_failed": "Não foi possível gerar o certificado",
"domain_created": "Domínio criado com êxito",
"domain_creation_failed": "Não foi possível criar o domínio",
"domain_deleted": "Domínio removido com êxito",
"domain_deletion_failed": "Não foi possível eliminar o domínio",
"domain_dyndns_already_subscribed": "Já subscreveu um domínio DynDNS",
"domain_dyndns_invalid": "Domínio inválido para ser utilizado com DynDNS",
"domain_dyndns_root_unknown": "Domínio root (administrador) DynDNS desconhecido",
"domain_exists": "O domínio já existe", "domain_exists": "O domínio já existe",
"domain_uninstall_app_first": "Existem uma ou mais aplicações instaladas neste domínio. Por favor desinstale-as antes de proceder com a remoção do domínio.",
"domain_unknown": "Domínio desconhecido",
"domain_zone_exists": "Ficheiro para zona DMZ já existe", "domain_zone_exists": "Ficheiro para zona DMZ já existe",
"domain_zone_not_found": "Ficheiro para zona DMZ não encontrado no domínio {:s}", "domain_zone_not_found": "Ficheiro para zona DMZ não encontrado no domínio {:s}",
"domain_creation_failed": "Não foi possível criar o domínio", "done": "Concluído.",
"domain_created": "Domínio criado com êxito", "downloading": "Transferência em curso...",
"domain_uninstall_app_first": "Existem uma ou mais aplicações instaladas neste domínio. Por favor desinstale-as antes de proceder com a remoção do domínio.",
"domain_deletion_failed": "Não foi possível eliminar o domínio",
"domain_deleted": "Domínio removido com êxito",
"no_internet_connection": "O servidor não está ligado à Internet",
"dyndns_key_generating": "A chave DNS está a ser gerada, isto pode demorar um pouco...",
"dyndns_unavailable": "Subdomínio DynDNS indisponível",
"dyndns_registration_failed": "Não foi possível registar o domínio DynDNS: {:s}",
"dyndns_registered": "Dom+inio DynDNS registado com êxito",
"dyndns_ip_update_failed": "Não foi possível atualizar o endereço IP a partir de DynDNS",
"dyndns_ip_updated": "Endereço IP atualizado com êxito a partir de DynDNS",
"dyndns_cron_installed": "Gestor de tarefas cron DynDNS instalado com êxito", "dyndns_cron_installed": "Gestor de tarefas cron DynDNS instalado com êxito",
"dyndns_cron_remove_failed": "Não foi possível remover o gestor de tarefas cron DynDNS", "dyndns_cron_remove_failed": "Não foi possível remover o gestor de tarefas cron DynDNS",
"dyndns_cron_removed": "Gestor de tarefas cron DynDNS removido com êxito", "dyndns_cron_removed": "Gestor de tarefas cron DynDNS removido com êxito",
"iptables_unavailable": "Não pode alterar aqui a iptables. Ou o seu kernel não o suporta ou está num espaço reservado.", "dyndns_ip_update_failed": "Não foi possível atualizar o endereço IP a partir de DynDNS",
"dyndns_ip_updated": "Endereço IP atualizado com êxito a partir de DynDNS",
"dyndns_key_generating": "A chave DNS está a ser gerada, isto pode demorar um pouco...",
"dyndns_registered": "Dom+inio DynDNS registado com êxito",
"dyndns_registration_failed": "Não foi possível registar o domínio DynDNS: {:s}",
"dyndns_unavailable": "Subdomínio DynDNS indisponível",
"executing_script": "A executar o script...",
"extracting": "Extração em curso...",
"field_invalid": "Campo inválido '{:s}'",
"firewall_reloaded": "Firewall recarregada com êxito", "firewall_reloaded": "Firewall recarregada com êxito",
"hook_choice_invalid": "Escolha inválida '{:s}'",
"hook_argument_missing": "Argumento em falta '{:s}'", "hook_argument_missing": "Argumento em falta '{:s}'",
"mountpoint_unknown": "Ponto de montagem desconhecido", "hook_choice_invalid": "Escolha inválida '{:s}'",
"unit_unknown": "Unidade desconhecida '{:s}'", "installation_complete": "Instalação concluída",
"monitor_period_invalid": "Período de tempo inválido", "installation_failed": "A instalação falhou",
"monitor_stats_no_update": "Não existem estatísticas de monitorização para atualizar", "iptables_unavailable": "Não pode alterar aqui a iptables. Ou o seu kernel não o suporta ou está num espaço reservado.",
"monitor_stats_file_not_found": "Ficheiro de estatísticas não encontrado", "ldap_initialized": "LDAP inicializada com êxito",
"monitor_stats_period_unavailable": "Não existem estatísticas disponíveis para este período", "license_undefined": "indefinido",
"monitor_enabled": "Monitorização do servidor ativada com êxito", "mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{:s}'",
"mail_domain_unknown": "Domínio de endereço de correio desconhecido '{:s}'",
"mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{:s}'",
"maindomain_change_failed": "Incapaz alterar o domínio raiz",
"maindomain_changed": "Domínio raiz alterado com êxito",
"monitor_disabled": "Monitorização do servidor parada com êxito", "monitor_disabled": "Monitorização do servidor parada com êxito",
"monitor_not_enabled": "A monitorização do servidor não está ativa", "monitor_enabled": "Monitorização do servidor ativada com êxito",
"monitor_glances_con_failed": "Não foi possível ligar ao servidor Glances", "monitor_glances_con_failed": "Não foi possível ligar ao servidor Glances",
"service_unknown": "Serviço desconhecido '{:s}'", "monitor_not_enabled": "A monitorização do servidor não está ativa",
"monitor_period_invalid": "Período de tempo inválido",
"monitor_stats_file_not_found": "Ficheiro de estatísticas não encontrado",
"monitor_stats_no_update": "Não existem estatísticas de monitorização para atualizar",
"monitor_stats_period_unavailable": "Não existem estatísticas disponíveis para este período",
"mountpoint_unknown": "Ponto de montagem desconhecido",
"mysql_db_creation_failed": "Criação da base de dados MySQL falhou",
"mysql_db_init_failed": "Inicialização da base de dados MySQL falhou",
"mysql_db_initialized": "Base de dados MySQL iniciada com êxito",
"new_domain_required": "Deve escrever um novo domínio principal",
"no_appslist_found": "Não foi encontrada a lista de aplicações",
"no_internet_connection": "O servidor não está ligado à Internet",
"packages_no_upgrade": "Não existem pacotes para atualizar",
"packages_upgrade_critical_later": "Os pacotes críticos ({:s}) serão atualizados depois",
"packages_upgrade_failed": "Não foi possível atualizar todos os pacotes",
"path_removal_failed": "Incapaz remover o caminho {:s}",
"pattern_domain": "Deve ser um nome de domínio válido (p.e. meu-dominio.org)",
"pattern_email": "Deve ser um endereço de correio válido (p.e. alguem@dominio.org)",
"pattern_firstname": "Deve ser um primeiro nome válido",
"pattern_lastname": "Deve ser um último nome válido",
"pattern_listname": "Apenas são permitidos caracteres alfanuméricos e travessões",
"pattern_password": "Deve ter no mínimo 3 caracteres",
"pattern_port": "Deve ser um número de porta válido (entre 0-65535)",
"pattern_username": "Must be lower-case alphanumeric and underscore characters only",
"service_add_failed": "Incapaz adicionar serviço '{:s}'", "service_add_failed": "Incapaz adicionar serviço '{:s}'",
"service_added": "Serviço adicionado com êxito", "service_added": "Serviço adicionado com êxito",
"service_already_started": "O serviço '{:s}' já está em execussão",
"service_already_stopped": "O serviço '{:s}' já está parado",
"service_cmd_exec_failed": "Incapaz executar o comando '{:s}'",
"service_disable_failed": "Incapaz desativar o serviço '{:s}'",
"service_disabled": "O serviço '{:s}' foi desativado com êxito",
"service_enable_failed": "Incapaz de ativar o serviço '{:s}'",
"service_enabled": "Serviço '{:s}' ativado com êxito",
"service_no_log": "Não existem registos para mostrar do serviço '{:s}'",
"service_remove_failed": "Incapaz de remover o serviço '{:s}'", "service_remove_failed": "Incapaz de remover o serviço '{:s}'",
"service_removed": "Serviço eliminado com êxito", "service_removed": "Serviço eliminado com êxito",
"service_start_failed": "Não foi possível iniciar o serviço '{:s}'", "service_start_failed": "Não foi possível iniciar o serviço '{:s}'",
"service_already_started": "O serviço '{:s}' já está em execussão",
"service_started": "O serviço '{:s} foi iniciado com êxito", "service_started": "O serviço '{:s} foi iniciado com êxito",
"service_stop_failed": "Incapaz parar o serviço '{:s}",
"service_already_stopped": "O serviço '{:s}' já está parado",
"service_stopped": "O serviço '{:s}' foi parado com êxito",
"service_enable_failed": "Incapaz de ativar o serviço '{:s}'",
"service_enabled": "Serviço '{:s}' ativado com êxito",
"service_disable_failed": "Incapaz desativar o serviço '{:s}'",
"service_disabled": "O serviço '{:s}' foi desativado com êxito",
"service_status_failed": "Incapaz determinar o estado do serviço '{:s}'", "service_status_failed": "Incapaz determinar o estado do serviço '{:s}'",
"service_no_log": "Não existem registos para mostrar do serviço '{:s}'", "service_stop_failed": "Incapaz parar o serviço '{:s}",
"service_cmd_exec_failed": "Incapaz executar o comando '{:s}'", "service_stopped": "O serviço '{:s}' foi parado com êxito",
"ldap_initialized": "LDAP inicializada com êxito", "service_unknown": "Serviço desconhecido '{:s}'",
"admin_password_change_failed": "Não foi possível alterar a senha", "ssowat_conf_generated": "Configuração SSOwat gerada com êxito",
"admin_password_changed": "Senha de administração alterada com êxito", "ssowat_conf_updated": "Configuração persistente SSOwat atualizada com êxito",
"new_domain_required": "Deve escrever um novo domínio principal", "system_upgraded": "Sistema atualizado com êxito",
"maindomain_change_failed": "Incapaz alterar o domínio raiz", "system_username_exists": "O utilizador já existe no registo do sistema",
"maindomain_changed": "Domínio raiz alterado com êxito", "unexpected_error": "Ocorreu um erro inesperado",
"yunohost_installing": "A instalar a YunoHost...", "unit_unknown": "Unidade desconhecida '{:s}'",
"update_cache_failed": "Não foi possível atualizar os cabeçalhos APT",
"updating_apt_cache": "A atualizar a lista de pacotes disponíveis...",
"upgrade_complete": "Atualização completa",
"upgrading_packages": "Atualização de pacotes em curso...",
"user_created": "Utilizador criado com êxito",
"user_creation_failed": "Não foi possível criar o utilizador",
"user_deleted": "Utilizador eliminado com êxito",
"user_deletion_failed": "Incapaz eliminar o utilizador",
"user_info_failed": "Incapaz obter informações sobre o utilizador",
"user_unknown": "Utilizador desconhecido",
"user_update_failed": "Não foi possível atualizar o utilizador",
"user_updated": "Utilizador atualizado com êxito",
"yunohost_already_installed": "A YunoHost já está instalada...", "yunohost_already_installed": "A YunoHost já está instalada...",
"yunohost_ca_creation_failed": "Incapaz criar o certificado de autoridade", "yunohost_ca_creation_failed": "Incapaz criar o certificado de autoridade",
"yunohost_configured": "YunoHost configurada com êxito", "yunohost_configured": "YunoHost configurada com êxito",
"updating_apt_cache": "A atualizar a lista de pacotes disponíveis...", "yunohost_installing": "A instalar a YunoHost...",
"update_cache_failed": "Não foi possível atualizar os cabeçalhos APT", "yunohost_not_installed": "YunoHost ainda não está corretamente configurado. Por favor execute as 'ferramentas pós-instalação yunohost'"
"packages_no_upgrade": "Não existem pacotes para atualizar",
"packages_upgrade_critical_later": "Os pacotes críticos ({:s}) serão atualizados depois",
"upgrading_packages": "Atualização de pacotes em curso...",
"packages_upgrade_failed": "Não foi possível atualizar todos os pacotes",
"system_upgraded": "Sistema atualizado com êxito",
"field_invalid": "Campo inválido '{:s}'",
"mail_domain_unknown": "Domínio de endereço de correio desconhecido '{:s}'",
"mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{:s}'",
"mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{:s}'",
"user_unknown": "Utilizador desconhecido",
"system_username_exists": "O utilizador já existe no registo do sistema",
"user_creation_failed": "Não foi possível criar o utilizador",
"user_created": "Utilizador criado com êxito",
"user_deletion_failed": "Incapaz eliminar o utilizador",
"user_deleted": "Utilizador eliminado com êxito",
"user_update_failed": "Não foi possível atualizar o utilizador",
"user_updated": "Utilizador atualizado com êxito",
"user_info_failed": "Incapaz obter informações sobre o utilizador",
"admin_password": "Senha de administração",
"ask_firstname": "Primeiro nome",
"ask_lastname": "Último nome",
"ask_email": "Correio eletrónico",
"ask_password": "Senha",
"ask_current_admin_password": "Senha de administração atual",
"ask_new_admin_password": "Senha de administração nova",
"ask_main_domain": "Domínio principal",
"ask_list_to_remove": "Lista para remover",
"pattern_username": "Must be lower-case alphanumeric and underscore characters only",
"pattern_firstname": "Deve ser um primeiro nome válido",
"pattern_lastname": "Deve ser um último nome válido",
"pattern_email": "Deve ser um endereço de correio válido (p.e. alguem@dominio.org)",
"pattern_password": "Deve ter no mínimo 3 caracteres",
"pattern_domain": "Deve ser um nome de domínio válido (p.e. meu-dominio.org)",
"pattern_listname": "Apenas são permitidos caracteres alfanuméricos e travessões",
"pattern_port": "Deve ser um número de porta válido (entre 0-65535)"
} }

1
locales/tr.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -24,15 +24,26 @@
## Packages versions ## Packages versions
def get_version(package): def get_version(package):
"""Get the version of package"""
from moulinette.utils import process from moulinette.utils import process
return process.check_output( return process.check_output(
"dpkg-query -W -f='${{Version}}' {0}".format(package) "dpkg-query -W -f='${{Version}}' {0}".format(package)
).strip() ).strip()
def get_versions(*args, **kwargs): def get_versions(*args, **kwargs):
"""Get the version of each YunoHost package"""
from collections import OrderedDict from collections import OrderedDict
return OrderedDict([ return OrderedDict([
('moulinette', get_version('moulinette')), ('moulinette', get_version('moulinette')),
('moulinette-yunohost', get_version('moulinette-yunohost')), ('yunohost', get_version('yunohost')),
('yunohost-admin', get_version('yunohost-admin')), ('yunohost-admin', get_version('yunohost-admin')),
]) ])
def has_min_version(min_version, package='yunohost', strict=False):
"""Check if a package has minimum version"""
from distutils.version import LooseVersion, StrictVersion
cmp_cls = StrictVersion if strict else LooseVersion
version = cmp_cls(get_version(package))
if version >= cmp_cls(min_version):
return True
return False

View file

@ -39,6 +39,9 @@ import subprocess
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from . import has_min_version
from .service import service_log
logger = getActionLogger('yunohost.app') logger = getActionLogger('yunohost.app')
repo_path = '/var/cache/yunohost/repo' repo_path = '/var/cache/yunohost/repo'
@ -70,7 +73,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/list.json) url -- URL of remote JSON list (default https://yunohost.org/official.json)
""" """
# Create app path if not exists # Create app path if not exists
@ -78,7 +81,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/list.json' url = 'https://yunohost.org/official.json'
name = 'yunohost' name = 'yunohost'
else: else:
if name is None: if name is None:
@ -166,24 +169,29 @@ def app_list(offset=None, limit=None, filter=None, raw=False):
sorted_app_dict[sorted_keys] = app_dict[sorted_keys] sorted_app_dict[sorted_keys] = app_dict[sorted_keys]
i = 0 i = 0
for app_id, app_info 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['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) installed = _is_installed(app_id)
if raw: if raw:
app_info['installed'] = installed app_info_dict['installed'] = installed
if installed: if installed:
app_info['status'] = _get_app_status(app_id) app_info_dict['status'] = _get_app_status(app_id)
list_dict[app_id] = app_info list_dict[app_id] = app_info_dict
else: else:
label = None
if installed:
app_info_dict_raw = app_info(app=app_id, raw=True)
label = app_info_dict_raw['settings']['label']
list_dict.append({ list_dict.append({
'id': app_id, 'id': app_id,
'name': app_info['manifest']['name'], 'name': app_info_dict['manifest']['name'],
'label': label,
'description': _value_for_locale( 'description': _value_for_locale(
app_info['manifest']['description']), app_info_dict['manifest']['description']),
# FIXME: Temporarly allow undefined license # FIXME: Temporarly allow undefined license
'license': app_info['manifest'].get('license', 'license': app_info_dict['manifest'].get('license',
m18n.n('license_undefined')), m18n.n('license_undefined')),
'installed': installed 'installed': installed
}) })
@ -207,11 +215,10 @@ def app_info(app, show_status=False, raw=False):
""" """
if not _is_installed(app): if not _is_installed(app):
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('app_not_installed', app)) m18n.n('app_not_installed', app=app))
if raw: if raw:
ret = app_list(filter=app, raw=True)[app] ret = app_list(filter=app, raw=True)[app]
with open(apps_setting_path + app +'/settings.yml') as f: ret['settings'] = _get_app_settings(app)
ret['settings'] = yaml.load(f)
return ret return ret
app_setting_path = apps_setting_path + app app_setting_path = apps_setting_path + app
@ -245,34 +252,43 @@ def app_map(app=None, raw=False, user=None):
app -- Specific app to map app -- Specific app to map
""" """
apps = []
result = {} result = {}
for app_id in os.listdir(apps_setting_path): if app is not None:
if app and (app != app_id): if not _is_installed(app):
raise MoulinetteError(errno.EINVAL,
m18n.n('app_not_installed', app=app))
apps = [app,]
else:
apps = os.listdir(apps_setting_path)
for app_id in apps:
app_settings = _get_app_settings(app_id)
if not app_settings:
continue continue
if user is not None:
app_dict = app_info(app=app_id, raw=True)
if ('mode' not in app_dict['settings']) or ('mode' in app_dict['settings'] and app_dict['settings']['mode'] == 'private'):
if 'allowed_users' in app_dict['settings'] and user not in app_dict['settings']['allowed_users'].split(','):
continue
with open(apps_setting_path + app_id +'/settings.yml') as f:
app_settings = yaml.load(f)
if 'domain' not in app_settings: if 'domain' not in app_settings:
continue continue
if user is not None:
if ('mode' not in app_settings \
or ('mode' in app_settings \
and app_settings['mode'] == 'private')) \
and 'allowed_users' in app_settings \
and user not in app_settings['allowed_users'].split(','):
continue
domain = app_settings['domain']
path = app_settings.get('path', '/')
if raw: if raw:
if app_settings['domain'] not in result: if domain not in result:
result[app_settings['domain']] = {} result[domain] = {}
result[app_settings['domain']][app_settings['path']] = { result[domain][path] = {
'label': app_settings['label'], 'label': app_settings['label'],
'id': app_settings['id'] 'id': app_settings['id']
} }
else: else:
result[app_settings['domain']+app_settings['path']] = app_settings['label'] result[domain + path] = app_settings['label']
return result return result
@ -307,18 +323,13 @@ def app_upgrade(auth, app=[], url=None, file=None):
installed = _is_installed(app_id) installed = _is_installed(app_id)
if not installed: if not installed:
raise MoulinetteError(errno.ENOPKG, raise MoulinetteError(errno.ENOPKG,
m18n.n('app_not_installed', app_id)) m18n.n('app_not_installed', app=app_id))
if app_id in upgraded_apps: if app_id in upgraded_apps:
continue continue
if '__' in app_id:
original_app_id = app_id[:app_id.index('__')]
else:
original_app_id = app_id
current_app_dict = app_info(app_id, raw=True) current_app_dict = app_info(app_id, raw=True)
new_app_dict = app_info(original_app_id, raw=True) new_app_dict = app_info(app_id, raw=True)
if file: if file:
manifest = _extract_app_from_file(file) manifest = _extract_app_from_file(file)
@ -337,9 +348,11 @@ def app_upgrade(auth, app=[], url=None, file=None):
continue continue
# Check min version # Check min version
if 'min_version' in manifest and __version__ < manifest['min_version']: if 'min_version' in manifest \
and not has_min_version(manifest['min_version']):
raise MoulinetteError(errno.EPERM, raise MoulinetteError(errno.EPERM,
m18n.n('app_recent_version_required', app_id)) m18n.n('app_recent_version_required',
app=app_id))
app_setting_path = apps_setting_path +'/'+ app_id app_setting_path = apps_setting_path +'/'+ app_id
@ -347,36 +360,20 @@ def app_upgrade(auth, app=[], url=None, file=None):
status = _get_app_status(app_id) status = _get_app_status(app_id)
status['remote'] = manifest.get('remote', None) status['remote'] = manifest.get('remote', None)
if original_app_id != app_id:
# Replace original_app_id with the forked one in scripts
for script in os.listdir(app_tmp_folder +'/scripts'):
#TODO: do it with sed ?
if script[:1] != '.':
with open(app_tmp_folder +'/scripts/'+ script, "r") as sources:
lines = sources.readlines()
with open(app_tmp_folder +'/scripts/'+ script, "w") as sources:
for line in lines:
sources.write(re.sub(r''+ original_app_id +'', app_id, line))
if 'hooks' in os.listdir(app_tmp_folder):
for hook in os.listdir(app_tmp_folder +'/hooks'):
#TODO: do it with sed ?
if hook[:1] != '.':
with open(app_tmp_folder +'/hooks/'+ hook, "r") as sources:
lines = sources.readlines()
with open(app_tmp_folder +'/hooks/'+ hook, "w") as sources:
for line in lines:
sources.write(re.sub(r''+ original_app_id +'', app_id, line))
# Clean hooks and add new ones # Clean hooks and add new ones
hook_remove(app_id) hook_remove(app_id)
if 'hooks' in os.listdir(app_tmp_folder): if 'hooks' in os.listdir(app_tmp_folder):
for hook in os.listdir(app_tmp_folder +'/hooks'): for hook in os.listdir(app_tmp_folder +'/hooks'):
hook_add(app_id, app_tmp_folder +'/hooks/'+ hook) hook_add(app_id, app_tmp_folder +'/hooks/'+ hook)
# Retrieve arguments list for upgrade script
# TODO: Allow to specify arguments
args_list = _parse_args_from_manifest(manifest, 'upgrade', auth=auth)
args_list.append(app_id)
# Execute App upgrade script # Execute App upgrade script
os.system('chown -hR admin: %s' % install_tmp) os.system('chown -hR admin: %s' % install_tmp)
if hook_exec(app_tmp_folder +'/scripts/upgrade') != 0: if hook_exec(app_tmp_folder +'/scripts/upgrade', args_list) != 0:
raise MoulinetteError(errno.EIO, m18n.n('installation_failed')) raise MoulinetteError(errno.EIO, m18n.n('installation_failed'))
else: else:
now = int(time.time()) now = int(time.time())
@ -394,7 +391,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
# So much win # So much win
upgraded_apps.append(app_id) upgraded_apps.append(app_id)
msignals.display(m18n.n('app_upgraded', app_id), 'success') msignals.display(m18n.n('app_upgraded', app=app_id), 'success')
if not upgraded_apps: if not upgraded_apps:
raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade')) raise MoulinetteError(errno.ENODATA, m18n.n('app_no_upgrade'))
@ -441,9 +438,11 @@ def app_install(auth, app, label=None, args=None):
app_id = manifest['id'] app_id = manifest['id']
# Check min version # Check min version
if 'min_version' in manifest and __version__ < manifest['min_version']: if 'min_version' in manifest \
and not has_min_version(manifest['min_version']):
raise MoulinetteError(errno.EPERM, raise MoulinetteError(errno.EPERM,
m18n.n('app_recent_version_required', app_id)) m18n.n('app_recent_version_required',
app=app_id))
# Check if app can be forked # Check if app can be forked
instance_number = _installed_instance_number(app_id, last=True) + 1 instance_number = _installed_instance_number(app_id, last=True) + 1
@ -452,30 +451,14 @@ def app_install(auth, app, label=None, args=None):
raise MoulinetteError(errno.EEXIST, raise MoulinetteError(errno.EEXIST,
m18n.n('app_already_installed', app_id)) m18n.n('app_already_installed', app_id))
app_id_forked = app_id + '__' + str(instance_number) # Change app_id to the forked app id
app_id = app_id + '__' + str(instance_number)
# Replace app_id with the new one in scripts # Retrieve arguments list for install script
for script in os.listdir(app_tmp_folder +'/scripts'): args_dict = {} if not args else \
#TODO: do it with sed ? dict(urlparse.parse_qsl(args, keep_blank_values=True))
if script[:1] != '.': args_list = _parse_args_from_manifest(manifest, 'install', args_dict, auth)
with open(app_tmp_folder +'/scripts/'+ script, "r") as sources: args_list.append(app_id)
lines = sources.readlines()
with open(app_tmp_folder +'/scripts/'+ script, "w") as sources:
for line in lines:
sources.write(re.sub(r''+ app_id +'', app_id_forked, line))
if 'hooks' in os.listdir(app_tmp_folder):
for hook in os.listdir(app_tmp_folder +'/hooks'):
#TODO: do it with sed ?
if hook[:1] != '.':
with open(app_tmp_folder +'/hooks/'+ hook, "r") as sources:
lines = sources.readlines()
with open(app_tmp_folder +'/hooks/'+ hook, "w") as sources:
for line in lines:
sources.write(re.sub(r''+ app_id +'', app_id_forked, line))
# Change app_id for the rest of the process
app_id = app_id_forked
# Prepare App settings # Prepare App settings
app_setting_path = apps_setting_path +'/'+ app_id app_setting_path = apps_setting_path +'/'+ app_id
@ -504,20 +487,13 @@ def app_install(auth, app, label=None, args=None):
os.system('chown -R admin: '+ app_tmp_folder) os.system('chown -R admin: '+ app_tmp_folder)
try:
if args is None:
args = ''
args_dict = dict(urlparse.parse_qsl(args, keep_blank_values=True))
except:
args_dict = {}
# Execute App install script # Execute App install script
os.system('chown -hR admin: %s' % install_tmp) os.system('chown -hR admin: %s' % install_tmp)
# 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))
try: try:
if hook_exec(app_tmp_folder + '/scripts/install', args_dict) == 0: if hook_exec(app_tmp_folder + '/scripts/install', args_list) == 0:
# Store app status # Store app status
with open(app_setting_path + '/status.json', 'w+') as f: with open(app_setting_path + '/status.json', 'w+') as f:
json.dump(status, f) json.dump(status, f)
@ -561,7 +537,8 @@ def app_remove(auth, app):
from yunohost.hook import hook_exec, hook_remove from yunohost.hook import hook_exec, hook_remove
if not _is_installed(app): if not _is_installed(app):
raise MoulinetteError(errno.EINVAL, m18n.n('app_not_installed', app)) raise MoulinetteError(errno.EINVAL,
m18n.n('app_not_installed', app=app))
app_setting_path = apps_setting_path + app app_setting_path = apps_setting_path + app
@ -574,8 +551,10 @@ def app_remove(auth, app):
os.system('chown -R admin: /tmp/yunohost_remove') os.system('chown -R admin: /tmp/yunohost_remove')
os.system('chmod -R u+rX /tmp/yunohost_remove') os.system('chmod -R u+rX /tmp/yunohost_remove')
if hook_exec('/tmp/yunohost_remove/scripts/remove') == 0: args_list = [app]
msignals.display(m18n.n('app_removed', app), 'success')
if hook_exec('/tmp/yunohost_remove/scripts/remove', args_list) == 0:
msignals.display(m18n.n('app_removed', app=app), 'success')
if os.path.exists(app_setting_path): shutil.rmtree(app_setting_path) if os.path.exists(app_setting_path): shutil.rmtree(app_setting_path)
shutil.rmtree('/tmp/yunohost_remove') shutil.rmtree('/tmp/yunohost_remove')
@ -595,47 +574,50 @@ def app_addaccess(auth, apps, users=[]):
from yunohost.user import user_list, user_info from yunohost.user import user_list, user_info
from yunohost.hook import hook_callback from yunohost.hook import hook_callback
result = {}
if not users: if not users:
users = user_list(auth)['users'].keys() users = user_list(auth)['users'].keys()
elif not isinstance(users, list):
if not isinstance(users, list): users = [users] users = [users,]
if not isinstance(apps, list): apps = [apps] if not isinstance(apps, list):
apps = [apps,]
for app in apps: for app in apps:
if not _is_installed(app): app_settings = _get_app_settings(app)
raise MoulinetteError(errno.EINVAL, if not app_settings:
m18n.n('app_not_installed', app)) continue
with open(apps_setting_path + app +'/settings.yml') as f:
app_settings = yaml.load(f)
if 'mode' not in app_settings: if 'mode' not in app_settings:
app_setting(app, 'mode', 'private') app_setting(app, 'mode', 'private')
app_settings['mode'] = 'private' app_settings['mode'] = 'private'
if app_settings['mode'] == 'private': if app_settings['mode'] == 'private':
allowed_users = set()
if 'allowed_users' in app_settings: if 'allowed_users' in app_settings:
new_users = app_settings['allowed_users'] allowed_users = set(app_settings['allowed_users'].split(','))
else:
new_users = ''
for allowed_user in users: for allowed_user in users:
if allowed_user not in new_users.split(','): if allowed_user not in allowed_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('{0}{1}'.format(
m18n.g('colon', m18n.n('user_unknown')),
allowed_user))
continue continue
if new_users == '': allowed_users.add(allowed_user)
new_users = allowed_user
else:
new_users = new_users +','+ allowed_user
app_setting(app, 'allowed_users', new_users.strip()) new_users = ','.join(allowed_users)
app_setting(app, 'allowed_users', new_users)
hook_callback('post_app_addaccess', args=[app, new_users]) hook_callback('post_app_addaccess', args=[app, new_users])
result[app] = allowed_users
app_ssowatconf(auth) app_ssowatconf(auth)
return { 'allowed_users': new_users.split(',') } return { 'allowed_users': result }
def app_removeaccess(auth, apps, users=[]): def app_removeaccess(auth, apps, users=[]):
@ -650,45 +632,43 @@ def app_removeaccess(auth, apps, users=[]):
from yunohost.user import user_list from yunohost.user import user_list
from yunohost.hook import hook_callback from yunohost.hook import hook_callback
result = {}
remove_all = False remove_all = False
if not users: if not users:
remove_all = True remove_all = True
if not isinstance(users, list): users = [users] elif not isinstance(users, list):
if not isinstance(apps, list): apps = [apps] users = [users,]
if not isinstance(apps, list):
apps = [apps,]
for app in apps: for app in apps:
new_users = '' app_settings = _get_app_settings(app)
if not app_settings:
continue
allowed_users = set()
if not _is_installed(app): if app_settings.get('skipped_uris', '') != '/':
raise MoulinetteError(errno.EINVAL,
m18n.n('app_not_installed', app))
with open(apps_setting_path + app +'/settings.yml') as f:
app_settings = yaml.load(f)
if 'skipped_uris' not in app_settings or app_settings['skipped_uris'] != '/':
if remove_all: if remove_all:
new_users = '' pass
elif 'allowed_users' in app_settings: elif 'allowed_users' in app_settings:
for allowed_user in app_settings['allowed_users'].split(','): for allowed_user in app_settings['allowed_users'].split(','):
if allowed_user not in users: if allowed_user not in users:
if new_users == '': allowed_users.add(allowed_user)
new_users = allowed_user
else:
new_users = new_users +','+ allowed_user
else: else:
new_users = '' for allowed_user in user_list(auth)['users'].keys():
for username in user_list(auth)['users'].keys(): if allowed_user not in users:
if username not in users: allowed_users.add(allowed_user)
if new_users == '':
new_users = username
new_users += ',' + username
app_setting(app, 'allowed_users', new_users.strip()) new_users = ','.join(allowed_users)
app_setting(app, 'allowed_users', new_users)
hook_callback('post_app_removeaccess', args=[app, new_users]) hook_callback('post_app_removeaccess', args=[app, new_users])
result[app] = allowed_users
app_ssowatconf(auth) app_ssowatconf(auth)
return { 'allowed_users': new_users.split(',') } return { 'allowed_users': result }
def app_clearaccess(auth, apps): def app_clearaccess(auth, apps):
@ -704,12 +684,9 @@ def app_clearaccess(auth, apps):
if not isinstance(apps, list): apps = [apps] if not isinstance(apps, list): apps = [apps]
for app in apps: for app in apps:
if not _is_installed(app): app_settings = _get_app_settings(app)
raise MoulinetteError(errno.EINVAL, if not app_settings:
m18n.n('app_not_installed', app)) continue
with open(apps_setting_path + app +'/settings.yml') as f:
app_settings = yaml.load(f)
if 'mode' in app_settings: if 'mode' in app_settings:
app_setting(app, 'mode', delete=True) app_setting(app, 'mode', delete=True)
@ -722,6 +699,29 @@ def app_clearaccess(auth, apps):
app_ssowatconf(auth) app_ssowatconf(auth)
def app_debug(app):
"""
Display debug informations for an app
Keyword argument:
app
"""
with open(apps_setting_path + app + '/manifest.json') as f:
manifest = json.loads(f.read())
return {
'name': manifest['id'],
'label': manifest['name'],
'services': [{
"name": x,
"logs": [{
"file_name": y,
"file_content": "\n".join(z),
} for (y, z) in sorted(service_log(x).items(), key=lambda x: x[0])],
} for x in sorted(manifest.get("services", []))]
}
def app_makedefault(auth, app, domain=None): def app_makedefault(auth, app, domain=None):
""" """
Redirect domain root to an app Redirect domain root to an app
@ -733,12 +733,7 @@ def app_makedefault(auth, app, domain=None):
""" """
from yunohost.domain import domain_list from yunohost.domain import domain_list
if not _is_installed(app): app_settings = _get_app_settings(app)
raise MoulinetteError(errno.EINVAL, m18n.n('app_not_installed', app))
with open(apps_setting_path + app +'/settings.yml') as f:
app_settings = yaml.load(f)
app_domain = app_settings['domain'] app_domain = app_settings['domain']
app_path = app_settings['path'] app_path = app_settings['path']
@ -781,23 +776,13 @@ def app_setting(app, key, value=None, delete=False):
delete -- Delete the key delete -- Delete the key
""" """
if not _is_installed(app): app_settings = _get_app_settings(app)
raise MoulinetteError(errno.EINVAL,
m18n.n('app_not_installed', app))
settings_file = apps_setting_path + app +'/settings.yml'
try:
with open(settings_file) as f:
app_settings = yaml.load(f)
except IOError:
# Do not fail if setting file is not there
app_settings = {}
if value is None and not delete: if value is None and not delete:
try: try:
return app_settings[key] return app_settings[key]
except: except:
logger.exception("cannot get app setting '%s' for '%s'", key, app) logger.info("cannot get app setting '%s' for '%s'", key, app)
return None return None
else: else:
yaml_settings=['redirected_urls','redirected_regex'] yaml_settings=['redirected_urls','redirected_regex']
@ -811,7 +796,8 @@ def app_setting(app, key, value=None, delete=False):
value=yaml.load(value) value=yaml.load(value)
app_settings[key] = value app_settings[key] = value
with open(settings_file, 'w') as f: with open(os.path.join(
apps_setting_path, app, 'settings.yml'), 'w') as f:
yaml.safe_dump(app_settings, f, default_flow_style=False) yaml.safe_dump(app_settings, f, default_flow_style=False)
@ -1011,6 +997,29 @@ def app_ssowatconf(auth):
msignals.display(m18n.n('ssowat_conf_generated'), 'success') msignals.display(m18n.n('ssowat_conf_generated'), 'success')
def _get_app_settings(app_id):
"""
Get settings of an installed app
Keyword arguments:
app_id -- The app id
"""
if not _is_installed(app_id):
raise MoulinetteError(errno.EINVAL,
m18n.n('app_not_installed', app=app_id))
try:
with open(os.path.join(
apps_setting_path, app_id, 'settings.yml')) as f:
settings = yaml.load(f)
if app_id == settings['id']:
return settings
except (IOError, TypeError, KeyError):
logger.exception(m18n.n('app_not_correctly_installed',
app=app_id))
return {}
def _get_app_status(app_id, format_date=False): def _get_app_status(app_id, format_date=False):
""" """
Get app status or create it if needed Get app status or create it if needed
@ -1304,6 +1313,116 @@ def _encode_string(value):
return value return value
def _parse_args_from_manifest(manifest, action, args={}, auth=None):
"""Parse arguments needed for an action from the manifest
Retrieve specified arguments for the action from the manifest, and parse
given args according to that. If some required arguments are not provided,
its values will be asked if interaction is possible.
Parsed arguments will be returned as a list of strings to pass directly
to the proper script.
Keyword arguments:
manifest -- The app manifest to use
action -- The action to retrieve arguments for
args -- A dictionnary of arguments to parse
"""
from yunohost.domain import domain_list
from yunohost.user import user_info
args_list = []
try:
action_args = manifest['arguments'][action]
except KeyError:
logger.debug("no arguments found for '%s' in manifest", action)
else:
for arg in action_args:
arg_name = arg['name']
arg_type = arg.get('type', 'string')
arg_default = arg.get('default', None)
arg_choices = arg.get('choices', [])
arg_value = None
# Transpose default value for boolean type and set it to
# false if not defined.
if arg_type == 'boolean':
arg_default = 1 if arg_default else 0
# Attempt to retrieve argument value
if arg_name in args:
arg_value = args[arg_name]
else:
if os.isatty(1) and 'ask' in arg:
# Retrieve proper ask string
ask_string = _value_for_locale(arg['ask'])
# Append extra strings
if arg_type == 'boolean':
ask_string += ' [0 | 1]'
elif arg_choices:
ask_string += ' [{0}]'.format(' | '.join(arg_choices))
if arg_default is not None:
ask_string += ' (default: {0})'.format(arg_default)
input_string = msignals.prompt(ask_string)
if (input_string == '' or input_string is None) \
and arg_default is not None:
arg_value = arg_default
else:
arg_value = input_string
elif arg_default is not None:
arg_value = arg_default
# Validate argument value
if (arg_value is None or arg_value == '') \
and not arg.get('optional', False):
raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_required', name=arg_name))
elif not arg_value:
args_list.append('')
continue
# Validate argument choice
if arg_choices and arg_value not in arg_choices:
raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_choice_invalid',
name=arg_name, choices=', '.join(arg_choices)))
# Validate argument type
if arg_type == 'domain':
if arg_value not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_invalid',
name=arg_name, error=m18n.n('domain_unknown')))
elif arg_type == 'user':
try:
user_info(auth, arg_value)
except MoulinetteError as e:
raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_invalid',
name=arg_name, error=e.strerror))
elif arg_type == 'app':
if not _is_installed(arg_value):
raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_invalid',
name=arg_name, error=m18n.n('app_unknown')))
elif arg_type == 'boolean':
if isinstance(arg_value, bool):
arg_value = 1 if arg_value else 0
else:
try:
arg_value = int(arg_value)
if arg_value not in [0, 1]:
raise ValueError()
except (TypeError, ValueError):
raise MoulinetteError(errno.EINVAL,
m18n.n('app_argument_choice_invalid',
name=arg_name, choices='0, 1'))
args_list.append(arg_value)
return args_list
def is_true(arg): def is_true(arg):
""" """
Convert a string into a boolean Convert a string into a boolean

View file

@ -32,6 +32,7 @@ import time
import tarfile import tarfile
import shutil import shutil
import subprocess import subprocess
from glob import glob
from collections import OrderedDict from collections import OrderedDict
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
@ -62,27 +63,27 @@ 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_callback, hook_exec from yunohost.hook import hook_info, hook_callback, hook_exec
tmp_dir = None tmp_dir = None
# Validate what to backup # Validate what to backup
if ignore_hooks and ignore_apps: if ignore_hooks and ignore_apps:
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('backup_action_required')) m18n.n('backup_action_required'))
# Validate and define backup name # Validate and define backup name
timestamp = int(time.time()) timestamp = int(time.time())
if not name: if not name:
name = str(timestamp) name = time.strftime('%Y%m%d-%H%M%S')
if name in backup_list()['archives']: if name in backup_list()['archives']:
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('backup_archive_name_exists')) m18n.n('backup_archive_name_exists'))
# Validate additional arguments # Validate additional arguments
if no_compress and not output_directory: if no_compress and not output_directory:
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('backup_output_directory_required')) m18n.n('backup_output_directory_required'))
if output_directory: if output_directory:
output_directory = os.path.abspath(output_directory) output_directory = os.path.abspath(output_directory)
@ -90,19 +91,17 @@ def backup_create(name=None, description=None, output_directory=None,
if output_directory.startswith(archives_path) or \ if output_directory.startswith(archives_path) or \
re.match(r'^/(|(bin|boot|dev|etc|lib|root|run|sbin|sys|usr|var)(|/.*))$', re.match(r'^/(|(bin|boot|dev|etc|lib|root|run|sbin|sys|usr|var)(|/.*))$',
output_directory): output_directory):
logger.error("forbidden output directory '%'", output_directory)
raise MoulinetteError(errno.EINVAL, raise MoulinetteError(errno.EINVAL,
m18n.n('backup_output_directory_forbidden')) m18n.n('backup_output_directory_forbidden'))
# Create the output directory # Create the output directory
if not os.path.isdir(output_directory): if not os.path.isdir(output_directory):
logger.info("creating output directory '%s'", output_directory) logger.debug("creating output directory '%s'", output_directory)
os.makedirs(output_directory, 0750) os.makedirs(output_directory, 0750)
# Check that output directory is empty # Check that output directory is empty
elif no_compress and os.listdir(output_directory): elif no_compress and os.listdir(output_directory):
logger.error("not empty output directory '%'", output_directory)
raise MoulinetteError(errno.EIO, raise MoulinetteError(errno.EIO,
m18n.n('backup_output_directory_not_empty')) m18n.n('backup_output_directory_not_empty'))
# Define temporary directory # Define temporary directory
if no_compress: if no_compress:
@ -114,8 +113,8 @@ def backup_create(name=None, description=None, output_directory=None,
if not tmp_dir: if not tmp_dir:
tmp_dir = "%s/tmp/%s" % (backup_path, name) tmp_dir = "%s/tmp/%s" % (backup_path, name)
if os.path.isdir(tmp_dir): if os.path.isdir(tmp_dir):
logger.warning("temporary directory for backup '%s' already exists", logger.debug("temporary directory for backup '%s' already exists",
tmp_dir) tmp_dir)
filesystem.rm(tmp_dir, recursive=True) filesystem.rm(tmp_dir, recursive=True)
filesystem.mkdir(tmp_dir, 0750, parents=True, uid='admin') filesystem.mkdir(tmp_dir, 0750, parents=True, uid='admin')
@ -124,7 +123,7 @@ def backup_create(name=None, description=None, output_directory=None,
if not ret['failed']: if not ret['failed']:
filesystem.rm(tmp_dir, True, True) filesystem.rm(tmp_dir, True, True)
else: else:
msignals.display(m18n.n('backup_cleaning_failed'), 'warning') logger.warning(m18n.n('backup_cleaning_failed'))
# Initialize backup info # Initialize backup info
info = { info = {
@ -136,9 +135,35 @@ def backup_create(name=None, description=None, output_directory=None,
# Run system hooks # Run system hooks
if not ignore_hooks: if not ignore_hooks:
msignals.display(m18n.n('backup_running_hooks')) # Check hooks availibility
hooks_ret = hook_callback('backup', hooks, args=[tmp_dir]) hooks_filtered = set()
info['hooks'] = hooks_ret['succeed'] if hooks:
for hook in hooks:
try:
hook_info('backup', hook)
except:
logger.error(m18n.n('backup_hook_unknown', hook=hook))
else:
hooks_filtered.add(hook)
if not hooks or hooks_filtered:
logger.info(m18n.n('backup_running_hooks'))
ret = hook_callback('backup', hooks_filtered, args=[tmp_dir])
if ret['succeed']:
info['hooks'] = ret['succeed']
# Save relevant restoration hooks
tmp_hooks_dir = tmp_dir + '/hooks/restore'
filesystem.mkdir(tmp_hooks_dir, 0750, True, uid='admin')
for h in ret['succeed'].keys():
try:
i = hook_info('restore', h)
except:
logger.warning(m18n.n('restore_hook_unavailable',
hook=h), exc_info=1)
else:
for f in i['hooks']:
shutil.copy(f['path'], tmp_hooks_dir)
# Backup apps # Backup apps
if not ignore_apps: if not ignore_apps:
@ -150,8 +175,7 @@ def backup_create(name=None, description=None, output_directory=None,
if apps: if apps:
for a in apps: for a in apps:
if a not in apps_list: if a not in apps_list:
logger.warning("app '%s' not found", a) logger.warning(m18n.n('unbackup_app', app=a))
msignals.display(m18n.n('unbackup_app', a), 'warning')
else: else:
apps_filtered.add(a) apps_filtered.add(a)
else: else:
@ -162,33 +186,18 @@ def backup_create(name=None, description=None, output_directory=None,
for app_id in apps_filtered: for app_id in apps_filtered:
app_setting_path = '/etc/yunohost/apps/' + app_id app_setting_path = '/etc/yunohost/apps/' + app_id
tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_id) # Check if the app has a backup and restore script
# Check if the app has a backup script
app_script = app_setting_path + '/scripts/backup' app_script = app_setting_path + '/scripts/backup'
if not os.path.isfile(app_script):
logger.warning("backup script '%s' not found", app_script)
msignals.display(m18n.n('unbackup_app', app_id),
'warning')
continue
# Copy the app restore script
app_restore_script = app_setting_path + '/scripts/restore' app_restore_script = app_setting_path + '/scripts/restore'
if os.path.isfile(app_script): if not os.path.isfile(app_script):
try: logger.warning(m18n.n('unbackup_app', app=app_id))
filesystem.mkdir(tmp_app_dir, 0750, True, uid='admin') continue
shutil.copy(app_restore_script, tmp_app_dir) elif not os.path.isfile(app_restore_script):
except: logger.warning(m18n.n('unrestore_app', app=app_id))
logger.exception("error while copying restore script of '%s'", app_id)
msignals.display(m18n.n('restore_app_copy_failed', app=app_id),
'warning')
else:
logger.warning("restore script '%s' not found", app_script)
msignals.display(m18n.n('unrestorable_app', app_id),
'warning')
tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_id)
tmp_app_bkp_dir = tmp_app_dir + '/backup' tmp_app_bkp_dir = tmp_app_dir + '/backup'
msignals.display(m18n.n('backup_running_app_script', app_id)) logger.info(m18n.n('backup_running_app_script', app=app_id))
try: try:
# Prepare backup directory for the app # Prepare backup directory for the app
filesystem.mkdir(tmp_app_bkp_dir, 0750, True, uid='admin') filesystem.mkdir(tmp_app_bkp_dir, 0750, True, uid='admin')
@ -196,11 +205,10 @@ def backup_create(name=None, description=None, output_directory=None,
# Copy app backup script in a temporary folder and execute it # Copy app backup script in a temporary folder and execute it
subprocess.call(['install', '-Dm555', app_script, tmp_script]) subprocess.call(['install', '-Dm555', app_script, tmp_script])
hook_exec(tmp_script, args=[tmp_app_bkp_dir, app_id]) hook_exec(tmp_script, args=[tmp_app_bkp_dir, app_id],
raise_on_error=True)
except: except:
logger.exception("error while executing backup of '%s'", app_id) logger.exception(m18n.n('backup_app_failed', app=app_id))
msignals.display(m18n.n('backup_app_failed', app=app_id),
'error')
# Cleaning app backup directory # Cleaning app backup directory
shutil.rmtree(tmp_app_dir, ignore_errors=True) shutil.rmtree(tmp_app_dir, ignore_errors=True)
else: else:
@ -214,10 +222,10 @@ def backup_create(name=None, description=None, output_directory=None,
finally: finally:
filesystem.rm(tmp_script, force=True) filesystem.rm(tmp_script, force=True)
# Check if something has been saved # Check if something has been saved
if ignore_hooks and not info['apps']: if not info['hooks'] and not info['apps']:
_clean_tmp_dir(1) _clean_tmp_dir(1)
raise MoulinetteError(errno.EINVAL, m18n.n('backup_nothings_done')) raise MoulinetteError(errno.EINVAL, m18n.n('backup_nothings_done'))
# Create backup info file # Create backup info file
with open("%s/info.json" % tmp_dir, 'w') as f: with open("%s/info.json" % tmp_dir, 'w') as f:
@ -225,7 +233,7 @@ def backup_create(name=None, description=None, output_directory=None,
# Create the archive # Create the archive
if not no_compress: if not no_compress:
msignals.display(m18n.n('backup_creating_archive')) logger.info(m18n.n('backup_creating_archive'))
archive_file = "%s/%s.tar.gz" % (output_directory, name) archive_file = "%s/%s.tar.gz" % (output_directory, name)
try: try:
tar = tarfile.open(archive_file, "w:gz") tar = tarfile.open(archive_file, "w:gz")
@ -238,17 +246,16 @@ def backup_create(name=None, description=None, output_directory=None,
try: try:
tar = tarfile.open(archive_file, "w:gz") tar = tarfile.open(archive_file, "w:gz")
except: except:
logger.exception("unable to open '%s' for writing " logger.debug("unable to open '%s' for writing",
"after creating directory '%s'", archive_file, exc_info=1)
archive_file, archives_path)
tar = None tar = None
else: else:
logger.exception("unable to open the archive '%s' for writing", logger.debug("unable to open '%s' for writing",
archive_file) archive_file, exc_info=1)
if tar is None: if tar is None:
_clean_tmp_dir(2) _clean_tmp_dir(2)
raise MoulinetteError(errno.EIO, raise MoulinetteError(errno.EIO,
m18n.n('backup_archive_open_failed')) m18n.n('backup_archive_open_failed'))
tar.add(tmp_dir, arcname='') tar.add(tmp_dir, arcname='')
tar.close() tar.close()
@ -260,7 +267,7 @@ def backup_create(name=None, description=None, output_directory=None,
if tmp_dir != output_directory: if tmp_dir != output_directory:
_clean_tmp_dir() _clean_tmp_dir()
msignals.display(m18n.n('backup_complete'), 'success') logger.success(m18n.n('backup_complete'))
# Return backup info # Return backup info
info['name'] = name info['name'] = name
@ -279,9 +286,13 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals
force -- Force restauration on an already installed system force -- Force restauration on an already installed system
""" """
from yunohost.hook import hook_add from yunohost.hook import hook_info, hook_callback, hook_exec
from yunohost.hook import hook_callback from yunohost.hook import custom_hook_folder
from yunohost.hook import hook_exec
# Validate what to restore
if ignore_hooks and ignore_apps:
raise MoulinetteError(errno.EINVAL,
m18n.n('restore_action_required'))
# Retrieve and open the archive # Retrieve and open the archive
info = backup_info(name) info = backup_info(name)
@ -289,37 +300,50 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals
try: try:
tar = tarfile.open(archive_file, "r:gz") tar = tarfile.open(archive_file, "r:gz")
except: except:
logger.exception("unable to open the archive '%s' for reading", logger.debug("cannot open backup archive '%s'",
archive_file) archive_file, exc_info=1)
raise MoulinetteError(errno.EIO, m18n.n('backup_archive_open_failed')) raise MoulinetteError(errno.EIO, m18n.n('backup_archive_open_failed'))
# Check temporary directory # Check temporary directory
tmp_dir = "%s/tmp/%s" % (backup_path, name) tmp_dir = "%s/tmp/%s" % (backup_path, name)
if os.path.isdir(tmp_dir): if os.path.isdir(tmp_dir):
logger.warning("temporary directory for restoration '%s' already exists", logger.debug("temporary directory for restoration '%s' already exists",
tmp_dir) tmp_dir)
os.system('rm -rf %s' % tmp_dir) os.system('rm -rf %s' % tmp_dir)
def _clean_tmp_dir(retcode=0):
ret = hook_callback('post_backup_restore', args=[tmp_dir, retcode])
if not ret['failed']:
filesystem.rm(tmp_dir, True, True)
else:
logger.warning(m18n.n('restore_cleaning_failed'))
# Extract the tarball # Extract the tarball
msignals.display(m18n.n('backup_extracting_archive')) logger.info(m18n.n('backup_extracting_archive'))
tar.extractall(tmp_dir) tar.extractall(tmp_dir)
tar.close() tar.close()
# Retrieve backup info # Retrieve backup info
info_file = "%s/info.json" % tmp_dir
try: try:
with open("%s/info.json" % tmp_dir, 'r') as f: with open(info_file, 'r') as f:
info = json.load(f) info = json.load(f)
except IOError: except IOError:
logger.error("unable to retrieve backup info from '%s/info.json'", logger.debug("unable to load '%s'", info_file, exc_info=1)
tmp_dir)
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive')) raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
else: else:
logger.info("restoring from backup '%s' created on %s", name, logger.debug("restoring from backup '%s' created on %s", name,
time.ctime(info['created_at'])) time.ctime(info['created_at']))
# Initialize restauration summary result
result = {
'apps': [],
'hooks': {},
}
# Check if YunoHost is installed # Check if YunoHost is installed
if os.path.isfile('/etc/yunohost/installed'): if os.path.isfile('/etc/yunohost/installed'):
msignals.display(m18n.n('yunohost_already_installed'), 'warning') logger.warning(m18n.n('yunohost_already_installed'))
if not force: if not force:
try: try:
# Ask confirmation for restoring # Ask confirmation for restoring
@ -340,73 +364,116 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals
with open("%s/yunohost/current_host" % tmp_dir, 'r') as f: with open("%s/yunohost/current_host" % tmp_dir, 'r') as f:
domain = f.readline().rstrip() domain = f.readline().rstrip()
except IOError: except IOError:
logger.error("unable to retrieve domain from '%s/yunohost/current_host'", logger.debug("unable to retrieve domain from "
tmp_dir) "'%s/yunohost/current_host'", tmp_dir, exc_info=1)
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive')) raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
logger.info("executing the post-install...") logger.debug("executing the post-install...")
tools_postinstall(domain, 'yunohost', True) tools_postinstall(domain, 'yunohost', True)
# Run hooks # Run system hooks
if not ignore_hooks: if not ignore_hooks:
if hooks is None or len(hooks)==0: # Filter hooks to execute
hooks=info['hooks'].keys() hooks_list = set(info['hooks'].keys())
_is_hook_in_backup = lambda h: True
hooks_filtered=list(set(hooks) & set(info['hooks'].keys())) if hooks:
hooks_unexecuted=set(hooks) - set(info['hooks'].keys()) def _is_hook_in_backup(h):
for hook in hooks_unexecuted: if h in hooks_list:
logger.warning("hook '%s' not in this backup", hook) return True
msignals.display(m18n.n('backup_hook_unavailable', hook), 'warning') logger.error(m18n.n('backup_archive_hook_not_exec', hook=h))
msignals.display(m18n.n('restore_running_hooks')) return False
hook_callback('restore', hooks_filtered, args=[tmp_dir]) else:
hooks = hooks_list
# Check hooks availibility
hooks_filtered = set()
for h in hooks:
if not _is_hook_in_backup(h):
continue
try:
hook_info('restore', h)
except:
tmp_hooks = glob('{:s}/hooks/restore/*-{:s}'.format(tmp_dir, h))
if not tmp_hooks:
logger.exception(m18n.n('restore_hook_unavailable', hook=h))
continue
# Add restoration hook from the backup to the system
# FIXME: Refactor hook_add and use it instead
restore_hook_folder = custom_hook_folder + 'restore'
filesystem.mkdir(restore_hook_folder, 755, True)
for f in tmp_hooks:
logger.debug("adding restoration hook '%s' to the system "
"from the backup archive '%s'", f, archive_file)
shutil.copy(f, restore_hook_folder)
hooks_filtered.add(h)
if hooks_filtered:
logger.info(m18n.n('restore_running_hooks'))
ret = hook_callback('restore', hooks_filtered, args=[tmp_dir])
result['hooks'] = ret['succeed']
# Add apps restore hook # Add apps restore hook
if not ignore_apps: if not ignore_apps:
from yunohost.app import _is_installed
# 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()
if not apps: if apps:
apps=apps_list for a in apps:
if a not in apps_list:
from yunohost.app import _is_installed logger.error(m18n.n('backup_archive_app_not_found', app=a))
for app_id in apps: else:
if app_id not in apps_list: apps_filtered.add(a)
logger.warning("app '%s' not found", app_id) else:
msignals.display(m18n.n('unrestore_app', app_id), 'warning') apps_filtered = apps_list
elif _is_installed(app_id):
logger.warning("app '%s' already installed", app_id)
msignals.display(m18n.n('restore_already_installed_app', app=app_id), 'warning')
elif not os.path.isfile('{:s}/apps/{:s}/restore'.format(tmp_dir, app_id)):
logger.warning("backup for '%s' doesn't contain a restore script", app_id)
msignals.display(m18n.n('no_restore_script', app=app_id), 'warning')
else:
apps_filtered.add(app_id)
for app_id in apps_filtered: for app_id in apps_filtered:
app_bkp_dir='{:s}/apps/{:s}'.format(tmp_dir, app_id) tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_id)
# Check if the app is not already installed
if _is_installed(app_id):
logger.error(m18n.n('restore_already_installed_app',
app=app_id))
continue
# Check if the app has a restore script
app_script = tmp_app_dir + '/settings/scripts/restore'
if not os.path.isfile(app_script):
logger.warning(m18n.n('unrestore_app', app=app_id))
continue
tmp_script = '/tmp/restore_' + app_id
app_setting_path = '/etc/yunohost/apps/' + app_id
logger.info(m18n.n('restore_running_app_script', app=app_id))
try: try:
# Copy app settings # Copy app settings and set permissions
app_setting_path = '/etc/yunohost/apps/' + app_id shutil.copytree(tmp_app_dir + '/settings', app_setting_path)
shutil.copytree(app_bkp_dir + '/settings', app_setting_path ) filesystem.chmod(app_setting_path, 0555, 0444, True)
filesystem.chmod(app_setting_path + '/settings.yml', 0400)
# Execute app restore script # Execute app restore script
app_restore_script=app_bkp_dir+'/restore' subprocess.call(['install', '-Dm555', app_script, tmp_script])
tmp_script = '/tmp/restore_%s_%s' % (name,app_id) hook_exec(tmp_script, args=[tmp_app_dir + '/backup', app_id],
subprocess.call(['install', '-Dm555', app_restore_script, tmp_script]) raise_on_error=True)
hook_exec(tmp_script, args=[app_bkp_dir+'/backup', app_id])
except: except:
logger.exception("error while restoring backup of '%s'", app_id) logger.exception(m18n.n('restore_app_failed', app=app_id))
msignals.display(m18n.n('restore_app_failed', app=app_id), # Cleaning app directory
'error') shutil.rmtree(app_setting_path, ignore_errors=True)
# Cleaning settings directory else:
shutil.rmtree(app_setting_path + '/settings', ignore_errors=True) result['apps'].append(app_id)
finally:
filesystem.rm(tmp_script, force=True)
# Check if something has been restored
if not result['hooks'] and not result['apps']:
_clean_tmp_dir(1)
raise MoulinetteError(errno.EINVAL, m18n.n('restore_nothings_done'))
# Remove temporary directory _clean_tmp_dir()
os.system('rm -rf %s' % tmp_dir) logger.success(m18n.n('restore_complete'))
msignals.display(m18n.n('restore_complete'), 'success') return result
def backup_list(with_info=False, human_readable=False): def backup_list(with_info=False, human_readable=False):
@ -423,8 +490,8 @@ def backup_list(with_info=False, human_readable=False):
try: try:
# Retrieve local archives # Retrieve local archives
archives = os.listdir(archives_path) archives = os.listdir(archives_path)
except OSError as e: except OSError:
logger.info("unable to iterate over local archives: %s", str(e)) logger.debug("unable to iterate over local archives", exc_info=1)
else: else:
# Iterate over local archives # Iterate over local archives
for f in archives: for f in archives:
@ -458,8 +525,8 @@ def backup_info(name, with_details=False, human_readable=False):
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):
logger.error("no local backup archive found at '%s'", archive_file) raise MoulinetteError(errno.EIO,
raise MoulinetteError(errno.EIO, m18n.n('backup_archive_name_unknown',name)) m18n.n('backup_archive_name_unknown', name=name))
info_file = "%s/%s.info.json" % (archives_path, name) info_file = "%s/%s.info.json" % (archives_path, name)
try: try:
@ -468,8 +535,7 @@ def backup_info(name, with_details=False, human_readable=False):
info = json.load(f) info = json.load(f)
except: except:
# TODO: Attempt to extract backup info file from tarball # TODO: Attempt to extract backup info file from tarball
logger.exception("unable to retrive backup info file '%s'", logger.debug("unable to load '%s'", info_file, exc_info=1)
info_file)
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive')) raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
size = os.path.getsize(archive_file) size = os.path.getsize(archive_file)
@ -506,15 +572,15 @@ def backup_delete(name):
info_file = "%s/%s.info.json" % (archives_path, name) info_file = "%s/%s.info.json" % (archives_path, name)
for backup_file in [archive_file,info_file]: for backup_file in [archive_file,info_file]:
if not os.path.isfile(backup_file): if not os.path.isfile(backup_file):
logger.error("no local backup archive found at '%s'", backup_file) raise MoulinetteError(errno.EIO,
raise MoulinetteError(errno.EIO, m18n.n('backup_archive_name_unknown', backup_file)) m18n.n('backup_archive_name_unknown', name=backup_file))
try: try:
os.remove(backup_file) os.remove(backup_file)
except: except:
logger.exception("unable to delete '%s'", backup_file) logger.debug("unable to delete '%s'", backup_file, exc_info=1)
raise MoulinetteError(errno.EIO, raise MoulinetteError(errno.EIO,
m18n.n('backup_delete_error',backup_file)) m18n.n('backup_delete_error', path=backup_file))
hook_callback('post_backup_delete', args=[name]) hook_callback('post_backup_delete', args=[name])
msignals.display(m18n.n('backup_deleted'), 'success') logger.success(m18n.n('backup_deleted'))

View file

@ -80,6 +80,7 @@ def domain_add(auth, domain, dyndns=False):
""" """
from yunohost.service import service_regenconf from yunohost.service import service_regenconf
from yunohost.hook import hook_callback
attr_dict = { 'objectClass' : ['mailDomain', 'top'] } attr_dict = { 'objectClass' : ['mailDomain', 'top'] }
try: try:
@ -172,6 +173,8 @@ def domain_add(auth, domain, dyndns=False):
except: pass except: pass
raise raise
hook_callback('post_domain_add', args=[domain])
msignals.display(m18n.n('domain_created'), 'success') msignals.display(m18n.n('domain_created'), 'success')
@ -185,6 +188,7 @@ def domain_remove(auth, domain, force=False):
""" """
from yunohost.service import service_regenconf from yunohost.service import service_regenconf
from yunohost.hook import hook_callback
if not force and domain not in domain_list(auth)['domains']: if not force and domain not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown')) raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
@ -211,4 +215,41 @@ def domain_remove(auth, domain, force=False):
service_regenconf(service='dnsmasq') service_regenconf(service='dnsmasq')
os.system('yunohost app ssowatconf > /dev/null 2>&1') os.system('yunohost app ssowatconf > /dev/null 2>&1')
hook_callback('post_domain_remove', args=[domain])
msignals.display(m18n.n('domain_deleted'), 'success') msignals.display(m18n.n('domain_deleted'), 'success')
def domain_dns_conf(domain):
"""
Generate DNS configuration for a domain
Keyword argument:
domain -- Domain name
"""
ip4 = urlopen("http://ip.yunohost.org").read().strip()
result = "@ 1400 IN A {ip4}\n* 1400 IN A {ip4}\n".format(ip4=ip4)
ip6 = None
try:
ip6 = urlopen("http://ip6.yunohost.org").read().strip()
except Exception:
pass
else:
result += "@ 1400 IN AAAA {ip6}\n* 1400 IN AAAA {ip6}\n".format(ip6=ip6)
result += "\n_xmpp-client._tcp 14400 IN SRV 0 5 5222 {domain}.\n_xmpp-server._tcp 14400 IN SRV 0 5 5269 {domain}.\n".format(domain=domain)
result += "muc 1800 IN CNAME @\npubsub 1800 IN CNAME @\nvjud 1800 IN CNAME @\n\n"
result += "@ 1400 IN MX 10 {domain}.\n".format(domain=domain)
if ip6 is None:
result += '@ 1400 IN TXT "v=spf1 a mx ip4:{ip4} -all"\n'.format(ip4=ip4)
else:
result += '@ 1400 IN TXT "v=spf1 a mx ip4:{ip4} ip6:{ip6} -all"\n'.format(ip4=ip4, ip6=ip6)
return result

View file

@ -24,7 +24,6 @@
Subscribe and Update DynDNS Hosts Subscribe and Update DynDNS Hosts
""" """
import os import os
import sys
import requests import requests
import re import re
import json import json
@ -97,7 +96,7 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None
# Send subscription # Send subscription
try: try:
r = requests.post('https://%s/key/%s' % (subscribe_host, base64.b64encode(key)), data={ 'subdomain': domain }) r = requests.post('https://%s/key/%s' % (subscribe_host, base64.b64encode(key)), data={ 'subdomain': domain })
except ConnectionError: except requests.ConnectionError:
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
if r.status_code != 201: if r.status_code != 201:
try: error = json.loads(r.text)['error'] try: error = json.loads(r.text)['error']
@ -130,7 +129,7 @@ def dyndns_update(dyn_host="dynhost.yunohost.org", domain=None, key=None, ip=Non
if ip is None: if ip is None:
try: try:
new_ip = requests.get('http://ip.yunohost.org').text new_ip = requests.get('http://ip.yunohost.org').text
except ConnectionError: except requests.ConnectionError:
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection')) raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
else: else:
new_ip = ip new_ip = ip

View file

@ -83,8 +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]
msignals.display(m18n.n('port_already_opened', port, ipv), logger.warning(m18n.n('port_already_opened', port, ipv))
'warning')
# 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)
@ -112,7 +111,7 @@ def firewall_disallow(protocol, port, ipv4_only=False, ipv6_only=False,
firewall = firewall_list(raw=True) firewall = firewall_list(raw=True)
# Validate port # Validate port
if ':' not in port: if not isinstance(port, int) and ':' not in port:
port = int(port) port = int(port)
# Validate protocols # Validate protocols
@ -141,8 +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]
msignals.display(m18n.n('port_already_closed', port, ipv), logger.warning(m18n.n('port_already_closed', port, ipv))
'warning')
# 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)
@ -188,10 +186,12 @@ def firewall_list(raw=False, by_ip_version=False, list_forwarded=False):
return ret return ret
def firewall_reload(): def firewall_reload(skip_upnp=False):
""" """
Reload all firewall rules Reload all firewall rules
Keyword arguments:
skip_upnp -- Do not refresh port forwarding using UPnP
""" """
from yunohost.hook import hook_callback from yunohost.hook import hook_callback
@ -206,15 +206,15 @@ def firewall_reload():
# Retrieve firewall rules and UPnP status # Retrieve firewall rules and UPnP status
firewall = firewall_list(raw=True) firewall = firewall_list(raw=True)
upnp = firewall_upnp()['enabled'] upnp = firewall_upnp()['enabled'] if not skip_upnp else False
# IPv4 # IPv4
try: try:
process.check_output("iptables -L") process.check_output("iptables -L")
except process.CalledProcessError as e: except process.CalledProcessError as e:
logger.info('iptables seems to be not available, it outputs:\n%s', logger.debug('iptables seems to be not available, it outputs:\n%s',
prependlines(e.output.rstrip(), '> ')) prependlines(e.output.rstrip(), '> '))
msignals.display(m18n.n('iptables_unavailable'), 'info') logger.warning(m18n.n('iptables_unavailable'))
else: else:
rules = [ rules = [
"iptables -F", "iptables -F",
@ -241,9 +241,9 @@ def firewall_reload():
try: try:
process.check_output("ip6tables -L") process.check_output("ip6tables -L")
except process.CalledProcessError as e: except process.CalledProcessError as e:
logger.info('ip6tables seems to be not available, it outputs:\n%s', logger.debug('ip6tables seems to be not available, it outputs:\n%s',
prependlines(e.output.rstrip(), '> ')) prependlines(e.output.rstrip(), '> '))
msignals.display(m18n.n('ip6tables_unavailable'), 'info') logger.warning(m18n.n('ip6tables_unavailable'))
else: else:
rules = [ rules = [
"ip6tables -F", "ip6tables -F",
@ -280,9 +280,9 @@ def firewall_reload():
os.system("service fail2ban restart") os.system("service fail2ban restart")
if errors: if errors:
msignals.display(m18n.n('firewall_rules_cmd_failed'), 'warning') logger.warning(m18n.n('firewall_rules_cmd_failed'))
else: else:
msignals.display(m18n.n('firewall_reloaded'), 'success') logger.success(m18n.n('firewall_reloaded'))
return firewall_list() return firewall_list()
@ -304,7 +304,7 @@ def firewall_upnp(action='status', no_refresh=False):
# Compatibility with previous version # Compatibility with previous version
if action == 'reload': if action == 'reload':
logger.warning("'reload' action is deprecated and will be removed") logger.info("'reload' action is deprecated and will be removed")
try: try:
# Remove old cron job # Remove old cron job
os.remove('/etc/cron.d/yunohost-firewall') os.remove('/etc/cron.d/yunohost-firewall')
@ -320,6 +320,11 @@ def firewall_upnp(action='status', no_refresh=False):
with open(upnp_cron_job, 'w+') as f: with open(upnp_cron_job, 'w+') as f:
f.write('*/50 * * * * root ' f.write('*/50 * * * * root '
'/usr/bin/yunohost firewall upnp status >>/dev/null\n') '/usr/bin/yunohost firewall upnp status >>/dev/null\n')
# Open port 1900 to receive discovery message
if 1900 not in firewall['ipv4']['UDP']:
firewall_allow('UDP', 1900, no_upnp=True, no_reload=True)
if not enabled:
firewall_reload(skip_upnp=True)
enabled = True enabled = True
elif action == 'disable' or (not enabled and action == 'status'): elif action == 'disable' or (not enabled and action == 'status'):
try: try:
@ -342,14 +347,14 @@ def firewall_upnp(action='status', no_refresh=False):
nb_dev = upnpc.discover() nb_dev = upnpc.discover()
logger.debug('found %d UPnP device(s)', int(nb_dev)) logger.debug('found %d UPnP device(s)', int(nb_dev))
if nb_dev < 1: if nb_dev < 1:
msignals.display(m18n.n('upnp_dev_not_found'), 'error') logger.error(m18n.n('upnp_dev_not_found'))
enabled = False enabled = False
else: else:
try: try:
# Select UPnP device # Select UPnP device
upnpc.selectigd() upnpc.selectigd()
except: except:
logger.exception('unable to select UPnP device') logger.info('unable to select UPnP device', exc_info=1)
enabled = False enabled = False
else: else:
# Iterate over ports # Iterate over ports
@ -367,11 +372,12 @@ def firewall_upnp(action='status', no_refresh=False):
upnpc.addportmapping(port, protocol, upnpc.lanaddr, upnpc.addportmapping(port, protocol, upnpc.lanaddr,
port, 'yunohost firewall: port %d' % port, '') port, 'yunohost firewall: port %d' % port, '')
except: except:
logger.exception('unable to add port %d using UPnP', logger.info('unable to add port %d using UPnP',
port) port, exc_info=1)
enabled = False enabled = False
if enabled != firewall['uPnP']['enabled']: if enabled != firewall['uPnP']['enabled']:
firewall = firewall_list(raw=True)
firewall['uPnP']['enabled'] = enabled firewall['uPnP']['enabled'] = enabled
# Make a backup and update firewall file # Make a backup and update firewall file
@ -382,13 +388,19 @@ def firewall_upnp(action='status', no_refresh=False):
if not no_refresh: if not no_refresh:
# Display success message if needed # Display success message if needed
if action == 'enable' and enabled: if action == 'enable' and enabled:
msignals.display(m18n.n('upnp_enabled'), 'success') logger.success(m18n.n('upnp_enabled'))
elif action == 'disable' and not enabled: elif action == 'disable' and not enabled:
msignals.display(m18n.n('upnp_disabled'), 'success') logger.success(m18n.n('upnp_disabled'))
# Make sure to disable UPnP # Make sure to disable UPnP
elif action != 'disable' and not enabled: elif action != 'disable' and not enabled:
firewall_upnp('disable', no_refresh=True) firewall_upnp('disable', no_refresh=True)
if not enabled and (action == 'enable' or 1900 in firewall['ipv4']['UDP']):
# Close unused port 1900
firewall_disallow('UDP', 1900, no_reload=True)
if not no_refresh:
firewall_reload(skip_upnp=True)
if action == 'enable' and not enabled: if action == 'enable' and not enabled:
raise MoulinetteError(errno.ENXIO, m18n.n('upnp_port_open_failed')) raise MoulinetteError(errno.ENXIO, m18n.n('upnp_port_open_failed'))
return { 'enabled': enabled } return { 'enabled': enabled }
@ -441,6 +453,6 @@ def _update_firewall_file(rules):
def _on_rule_command_error(returncode, cmd, output): def _on_rule_command_error(returncode, cmd, output):
"""Callback for rules commands error""" """Callback for rules commands error"""
# Log error and continue commands execution # Log error and continue commands execution
logger.error('"%s" returned non-zero exit status %d:\n%s', logger.info('"%s" returned non-zero exit status %d:\n%s',
cmd, returncode, prependlines(output.rstrip(), '> ')) cmd, returncode, prependlines(output.rstrip(), '> '))
return True return True

View file

@ -29,14 +29,15 @@ import re
import json import json
import errno import errno
import subprocess import subprocess
from glob import iglob
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger from moulinette.utils import log
hook_folder = '/usr/share/yunohost/hooks/' hook_folder = '/usr/share/yunohost/hooks/'
custom_hook_folder = '/etc/yunohost/hooks.d/' custom_hook_folder = '/etc/yunohost/hooks.d/'
logger = getActionLogger('yunohost.hook') logger = log.getActionLogger('yunohost.hook')
def hook_add(app, file): def hook_add(app, file):
@ -77,6 +78,46 @@ def hook_remove(app):
except OSError: pass except OSError: pass
def hook_info(action, name):
"""
Get information about a given hook
Keyword argument:
action -- Action name
name -- Hook name
"""
hooks = []
priorities = set()
# Search in custom folder first
for h in iglob('{:s}{:s}/*-{:s}'.format(
custom_hook_folder, action, name)):
priority, _ = _extract_filename_parts(os.path.basename(h))
priorities.add(priority)
hooks.append({
'priority': priority,
'path': h,
})
# Append non-overwritten system hooks
for h in iglob('{:s}{:s}/*-{:s}'.format(
hook_folder, action, name)):
priority, _ = _extract_filename_parts(os.path.basename(h))
if priority not in priorities:
hooks.append({
'priority': priority,
'path': h,
})
if not hooks:
raise MoulinetteError(errno.EINVAL, m18n.n('hook_name_unknown', name))
return {
'action': action,
'name': name,
'hooks': hooks,
}
def hook_list(action, list_by='name', show_info=False): def hook_list(action, list_by='name', show_info=False):
""" """
List available hooks for an action List available hooks for an action
@ -194,7 +235,7 @@ def hook_callback(action, hooks=[], args=None):
if key == n or key.startswith("%s_" % n) \ if key == n or key.startswith("%s_" % n) \
and key not in all_hooks: and key not in all_hooks:
all_hooks.append(key) all_hooks.append(key)
# Iterate over given hooks names list # Iterate over given hooks names list
for n in all_hooks: for n in all_hooks:
try: try:
@ -223,14 +264,9 @@ def hook_callback(action, hooks=[], args=None):
state = 'succeed' state = 'succeed'
filename = '%s-%s' % (priority, name) filename = '%s-%s' % (priority, name)
try: try:
ret = hook_exec(info['path'], args=args) hook_exec(info['path'], args=args, raise_on_error=True)
except: except MoulinetteError as e:
logger.exception("error while executing hook '%s'", logger.error(str(e))
info['path'])
state = 'failed'
if ret != 0:
logger.error("error while executing hook '%s', retcode: %d",
info['path'], ret)
state = 'failed' state = 'failed'
try: try:
result[state][name].append(info['path']) result[state][name].append(info['path'])
@ -239,111 +275,67 @@ def hook_callback(action, hooks=[], args=None):
return result return result
def hook_check(file): def hook_exec(path, args=None, raise_on_error=False, no_trace=False):
"""
Parse the script file and get arguments
Keyword argument:
file -- File to check
"""
try:
with open(file[:file.index('scripts/')] + 'manifest.json') as f:
manifest = json.loads(str(f.read()))
except:
raise MoulinetteError(errno.EIO, m18n.n('app_manifest_invalid'))
action = file[file.index('scripts/') + 8:]
if 'arguments' in manifest and action in manifest['arguments']:
return manifest['arguments'][action]
else:
return {}
def hook_exec(file, args=None):
""" """
Execute hook from a file with arguments Execute hook from a file with arguments
Keyword argument: Keyword argument:
file -- Script to execute path -- Path of the script to execute
args -- 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
no_trace -- Do not print each command that will be executed
""" """
from moulinette.utils.stream import NonBlockingStreamReader from moulinette.utils.process import call_async_output
from yunohost.app import _value_for_locale from yunohost.app import _value_for_locale
if isinstance(args, list): # Validate hook path
arg_list = args if path[0] != '/':
else: path = os.path.realpath(path)
required_args = hook_check(file) if not os.path.isfile(path):
if args is None: raise MoulinetteError(errno.EIO, m18n.g('file_not_exist'))
args = {}
arg_list = [] # Construct command variables
for arg in required_args: cmd_fdir, cmd_fname = os.path.split(path)
if arg['name'] in args: cmd_fname = './{0}'.format(cmd_fname)
if 'choices' in arg and args[arg['name']] not in arg['choices']:
raise MoulinetteError(errno.EINVAL,
m18n.n('hook_choice_invalid', args[arg['name']]))
arg_list.append(args[arg['name']])
else:
if os.isatty(1) and 'ask' in arg:
# Retrieve proper ask string
ask_string = _value_for_locale(arg['ask'])
# Append extra strings cmd_args = ''
if 'choices' in arg: if args and isinstance(args, list):
ask_string += ' ({:s})'.format('|'.join(arg['choices']))
if 'default' in arg:
ask_string += ' (default: {:s})'.format(arg['default'])
input_string = msignals.prompt(ask_string)
if input_string == '' and 'default' in arg:
input_string = arg['default']
arg_list.append(input_string)
elif 'default' in arg:
arg_list.append(arg['default'])
else:
raise MoulinetteError(errno.EINVAL,
m18n.n('hook_argument_missing', arg['name']))
file_path = "./"
if "/" in file and file[0:2] != file_path:
file_path = os.path.dirname(file)
file = file.replace(file_path +"/", "")
#TODO: Allow python script
arg_str = ''
if arg_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
arg_str = '"{:s}"'.format('" "'.join(str(s) for s in arg_list)) cmd_args = '"{:s}"'.format('" "'.join(str(s) for s in args))
msignals.display(m18n.n('executing_script')) # Construct command to execute
command = ['sudo', '-u', 'admin', '-H', 'sh', '-c']
if no_trace:
cmd = 'cd "{0:s}" && /bin/bash "{1:s}" {2:s}'
else:
# use xtrace on fd 7 which is redirected to stdout
cmd = 'cd "{0:s}" && BASH_XTRACEFD=7 /bin/bash -x "{1:s}" {2:s} 7>&1'
command.append(cmd.format(cmd_fdir, cmd_fname, cmd_args))
p = subprocess.Popen( if logger.isEnabledFor(log.DEBUG):
['sudo', '-u', 'admin', '-H', 'sh', '-c', 'cd "{:s}" && ' \ logger.info(m18n.n('executing_command', command=' '.join(command)))
'/bin/bash -x "{:s}" {:s}'.format( else:
file_path, file, arg_str)], logger.info(m18n.n('executing_script', script='{0}/{1}'.format(
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cmd_fdir, cmd_fname)))
shell=False)
# Wrap and get process ouput # Define output callbacks and call command
stream = NonBlockingStreamReader(p.stdout) callbacks = (
while True: lambda l: logger.info(l.rstrip()),
line = stream.readline(True, 0.1) lambda l: logger.warning(l.rstrip()),
if not line: )
# Check if process has terminated returncode = call_async_output(command, callbacks, shell=False)
returncode = p.poll()
if returncode is not None: # Check and return process' return code
break if returncode is None:
if raise_on_error:
raise MoulinetteError(m18n.n('hook_exec_not_terminated'))
else: else:
msignals.display(line.rstrip(), 'log') logger.error(m18n.n('hook_exec_not_terminated'))
stream.close() return 1
elif raise_on_error and returncode != 0:
raise MoulinetteError(m18n.n('hook_exec_failed'))
return returncode return returncode

View file

@ -34,6 +34,7 @@ import difflib
import hashlib import hashlib
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
from moulinette.utils import log
template_dir = os.getenv( template_dir = os.getenv(
'YUNOHOST_TEMPLATE_DIR', 'YUNOHOST_TEMPLATE_DIR',
@ -44,6 +45,9 @@ conf_backup_dir = os.getenv(
'/home/yunohost.backup/conffiles' '/home/yunohost.backup/conffiles'
) )
logger = log.getActionLogger('yunohost.service')
def service_add(name, status=None, log=None, runlevel=None): def service_add(name, status=None, log=None, runlevel=None):
""" """
Add a custom service Add a custom service
@ -73,7 +77,7 @@ def service_add(name, status=None, log=None, runlevel=None):
except: except:
raise MoulinetteError(errno.EIO, m18n.n('service_add_failed', name)) raise MoulinetteError(errno.EIO, m18n.n('service_add_failed', name))
msignals.display(m18n.n('service_added'), 'success') logger.success(m18n.n('service_added'))
def service_remove(name): def service_remove(name):
@ -96,7 +100,7 @@ def service_remove(name):
except: except:
raise MoulinetteError(errno.EIO, m18n.n('service_remove_failed', name)) raise MoulinetteError(errno.EIO, m18n.n('service_remove_failed', name))
msignals.display(m18n.n('service_removed'), 'success') logger.success(m18n.n('service_removed'))
def service_start(names): def service_start(names):
@ -111,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):
msignals.display(m18n.n('service_started', name), 'success') logger.success(m18n.n('service_started', 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', name))
msignals.display(m18n.n('service_already_started', name)) logger.info(m18n.n('service_already_started', name))
def service_stop(names): def service_stop(names):
@ -131,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):
msignals.display(m18n.n('service_stopped', name), 'success') logger.success(m18n.n('service_stopped', 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', name))
msignals.display(m18n.n('service_already_stopped', name)) logger.info(m18n.n('service_already_stopped', name))
def service_enable(names): def service_enable(names):
@ -151,7 +155,7 @@ 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):
msignals.display(m18n.n('service_enabled', name), 'success') logger.success(m18n.n('service_enabled', name))
else: else:
raise MoulinetteError(errno.EPERM, raise MoulinetteError(errno.EPERM,
m18n.n('service_enable_failed', name)) m18n.n('service_enable_failed', name))
@ -169,7 +173,7 @@ 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):
msignals.display(m18n.n('service_disabled', name), 'success') logger.success(m18n.n('service_disabled', name))
else: else:
raise MoulinetteError(errno.EPERM, raise MoulinetteError(errno.EPERM,
m18n.n('service_disable_failed', name)) m18n.n('service_disable_failed', name))
@ -217,8 +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():
msignals.display(m18n.n('service_status_failed', name), logger.warning(m18n.n('service_status_failed', name))
'warning')
else: else:
result[name]['status'] = 'inactive' result[name]['status'] = 'inactive'
else: else:
@ -288,7 +291,7 @@ def service_regenconf(service=None, force=False):
hook_callback('conf_regen', [service], args=[force]) hook_callback('conf_regen', [service], args=[force])
else: else:
hook_callback('conf_regen', args=[force]) hook_callback('conf_regen', args=[force])
msignals.display(m18n.n('services_configured'), 'success') logger.success(m18n.n('services_configured'))
def _run_service_command(action, service): def _run_service_command(action, service):
@ -317,8 +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?
msignals.display(m18n.n('service_cmd_exec_failed', ' '.join(e.cmd)), logger.warning(m18n.n('service_cmd_exec_failed', ' '.join(e.cmd)))
'warning')
return False return False
return True return True
@ -467,12 +469,9 @@ def service_saferemove(service, conf_file, force=False):
else: else:
services[service]['conffiles'][conf_file] = previous_hash services[service]['conffiles'][conf_file] = previous_hash
os.remove(conf_backup_file) os.remove(conf_backup_file)
if os.isatty(1) and \ if len(previous_hash) == 32 or previous_hash[-32:] != current_hash:
(len(previous_hash) == 32 or previous_hash[-32:] != current_hash): logger.warning(m18n.n('service_configuration_conflict',
msignals.display( file=conf_file))
m18n.n('service_configuration_changed', conf_file),
'warning'
)
_save_services(services) _save_services(services)
@ -495,7 +494,7 @@ def service_safecopy(service, new_conf_file, conf_file, force=False):
services = _get_services() services = _get_services()
if not os.path.exists(new_conf_file): if not os.path.exists(new_conf_file):
raise MoulinetteError(errno.EIO, m18n.n('no_such_conf_file', new_conf_file)) raise MoulinetteError(errno.EIO, m18n.n('no_such_conf_file', file=new_conf_file))
with open(new_conf_file, 'r') as f: with open(new_conf_file, 'r') as f:
new_conf = ''.join(f.readlines()).rstrip() new_conf = ''.join(f.readlines()).rstrip()
@ -509,8 +508,7 @@ def service_safecopy(service, new_conf_file, conf_file, force=False):
) )
process.wait() process.wait()
else: else:
msignals.display(m18n.n('service_add_configuration', conf_file), logger.info(m18n.n('service_add_configuration', file=conf_file))
'info')
# Add the service if it does not exist # Add the service if it does not exist
if service not in services.keys(): if service not in services.keys():
@ -539,15 +537,10 @@ def service_safecopy(service, new_conf_file, conf_file, force=False):
else: else:
new_hash = previous_hash new_hash = previous_hash
if (len(previous_hash) == 32 or previous_hash[-32:] != current_hash): if (len(previous_hash) == 32 or previous_hash[-32:] != current_hash):
msignals.display( logger.warning('{0} {1}'.format(
m18n.n('service_configuration_conflict', conf_file), m18n.n('service_configuration_conflict', file=conf_file),
'warning' m18n.n('show_diff', diff=''.join(diff))))
)
print('\n' + conf_file)
for line in diff:
print(line.strip())
print('')
# Remove the backup file if the configuration has not changed # Remove the backup file if the configuration has not changed
if new_hash == previous_hash: if new_hash == previous_hash:
try: try:

View file

@ -269,13 +269,13 @@ def tools_postinstall(domain, password, ignore_dyndns=False):
# Enable UPnP silently and reload firewall # Enable UPnP silently and reload firewall
firewall_upnp('enable', no_refresh=True) firewall_upnp('enable', no_refresh=True)
firewall_reload()
# Enable iptables at boot time
os.system('update-rc.d yunohost-firewall defaults')
os.system('touch /etc/yunohost/installed') os.system('touch /etc/yunohost/installed')
# Enable and start YunoHost firewall at boot time
os.system('update-rc.d yunohost-firewall enable')
os.system('service yunohost-firewall start')
service_regenconf(force=True) service_regenconf(force=True)
msignals.display(m18n.n('yunohost_configured'), 'success') msignals.display(m18n.n('yunohost_configured'), 'success')
@ -373,8 +373,8 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False):
# If API call # If API call
if is_api: if is_api:
critical_packages = ("moulinette", "moulinette-yunohost", critical_packages = ("moulinette", "yunohost",
"yunohost-admin", "yunohost-config-nginx", "ssowat", "python") "yunohost-admin", "ssowat", "python")
critical_upgrades = set() critical_upgrades = set()
for pkg in cache.get_changes(): for pkg in cache.get_changes():