mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge remote-tracking branch 'upstream/unstable' into unstable
Conflicts: data/hooks/restore/05-conf_ldap
This commit is contained in:
commit
ea5ffb8498
98 changed files with 3862 additions and 2272 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -31,4 +31,4 @@ pip-log.txt
|
|||
.mr.developer.cfg
|
||||
|
||||
# moulinette lib
|
||||
lib/yunohost/locales
|
||||
src/yunohost/locales
|
||||
|
|
2
README.md
Normal file
2
README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
Please report issues here (no registration needed):
|
||||
https://dev.yunohost.org/projects/yunohost/issues
|
172
bin/yunohost
172
bin/yunohost
|
@ -7,27 +7,28 @@ import os
|
|||
# Either we are in a development environment or not
|
||||
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
|
||||
LOGGERS_LEVEL = 'INFO'
|
||||
TTY_LOG_LEVEL = 'SUCCESS'
|
||||
|
||||
# Handlers that will be used by loggers
|
||||
# - file: log to the file LOG_DIR/LOG_FILE
|
||||
# - console: log to stderr
|
||||
LOGGERS_HANDLERS = ['file']
|
||||
# - tty: log to current tty
|
||||
LOGGERS_HANDLERS = ['file', 'tty']
|
||||
|
||||
# Directory and file to be used by logging
|
||||
LOG_DIR = '/var/log/yunohost'
|
||||
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 -----------------------------------
|
||||
|
||||
|
@ -40,83 +41,121 @@ def _die(message, title='Error:'):
|
|||
print('%s %s' % (colorize(title, 'red'), message))
|
||||
sys.exit(1)
|
||||
|
||||
def _check_in_devel():
|
||||
"""Check and load if needed development environment"""
|
||||
global IN_DEVEL, LOG_DIR
|
||||
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)
|
||||
def _parse_cli_args():
|
||||
"""Parse additional arguments for the cli"""
|
||||
import argparse
|
||||
|
||||
# Update global variables
|
||||
IN_DEVEL = True
|
||||
LOG_DIR = '%s/log' % basedir
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument('--no-cache',
|
||||
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():
|
||||
"""Parse additional arguments and return remaining ones"""
|
||||
argv = list(sys.argv)
|
||||
argv.pop(0)
|
||||
opts, args = parser.parse_known_args()
|
||||
|
||||
if '--no-cache' in argv:
|
||||
global USE_CACHE
|
||||
USE_CACHE = False
|
||||
argv.remove('--no-cache')
|
||||
if '--json' in argv:
|
||||
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
|
||||
# output compatibility
|
||||
if opts.plain:
|
||||
opts.output_as = 'plain'
|
||||
elif opts.json:
|
||||
opts.output_as = 'json'
|
||||
|
||||
def _init_moulinette():
|
||||
return (parser, opts, args)
|
||||
|
||||
def _init_moulinette(debug=False, verbose=False, quiet=False):
|
||||
"""Configure logging and initialize the moulinette"""
|
||||
from moulinette import init
|
||||
|
||||
# Define loggers handlers
|
||||
handlers = set(LOGGERS_HANDLERS)
|
||||
if quiet and 'tty' in handlers:
|
||||
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
|
||||
logging = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': True,
|
||||
'formatters': {
|
||||
'simple': {
|
||||
'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s - %(message)s'
|
||||
'tty-debug': {
|
||||
'format': '%(relativeCreated)-4d %(fmessage)s'
|
||||
},
|
||||
'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': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'simple',
|
||||
'stream': 'ext://sys.stderr',
|
||||
'tty': {
|
||||
'level': tty_level,
|
||||
'class': 'moulinette.interfaces.cli.TTYHandler',
|
||||
'formatter': 'tty-debug' if debug else '',
|
||||
},
|
||||
'file': {
|
||||
'class': 'logging.FileHandler',
|
||||
'formatter': 'precise',
|
||||
'filename': '%s/%s' % (LOG_DIR, LOG_FILE),
|
||||
'filters': ['action'],
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'moulinette': {
|
||||
'level': LOGGERS_LEVEL,
|
||||
'handlers': LOGGERS_HANDLERS,
|
||||
},
|
||||
'yunohost': {
|
||||
'level': LOGGERS_LEVEL,
|
||||
'handlers': LOGGERS_HANDLERS,
|
||||
'level': level,
|
||||
'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 ----------------------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
_check_in_devel()
|
||||
args = _parse_argv()
|
||||
_init_moulinette()
|
||||
parser, opts, args = _parse_cli_args()
|
||||
_init_moulinette(opts.debug, opts.verbose, opts.quiet)
|
||||
|
||||
# Check that YunoHost is installed
|
||||
if not os.path.isfile('/etc/yunohost/installed') and \
|
||||
|
@ -163,6 +201,8 @@ if __name__ == '__main__':
|
|||
|
||||
# Execute the action
|
||||
from moulinette import cli
|
||||
ret = cli(_retrieve_namespaces(), args, use_cache=USE_CACHE,
|
||||
print_json=PRINT_JSON, print_plain=PRINT_PLAIN)
|
||||
ret = cli(_retrieve_namespaces(), args,
|
||||
use_cache=opts.use_cache, output_as=opts.output_as,
|
||||
password=opts.password, parser_kwargs={'top_parser': parser}
|
||||
)
|
||||
sys.exit(ret)
|
||||
|
|
167
bin/yunohost-api
167
bin/yunohost-api
|
@ -7,24 +7,32 @@ import os.path
|
|||
# Either we are in a development environment or not
|
||||
IN_DEVEL = False
|
||||
|
||||
# Either cache has to be used inside the moulinette or not
|
||||
USE_CACHE = True
|
||||
|
||||
# Either WebSocket has to be installed by the moulinette or not
|
||||
USE_WEBSOCKET = True
|
||||
# Default server configuration
|
||||
DEFAULT_HOST = 'localhost'
|
||||
DEFAULT_PORT = 6787
|
||||
|
||||
# Level for which loggers will log
|
||||
LOGGERS_LEVEL = 'INFO'
|
||||
|
||||
# Handlers that will be used by loggers
|
||||
# - file: log to the file LOG_DIR/LOG_FILE
|
||||
# - api: serve logs through the api
|
||||
# - console: log to stderr
|
||||
LOGGERS_HANDLERS = ['file']
|
||||
LOGGERS_HANDLERS = ['file', 'api']
|
||||
|
||||
# Directory and file to be used by logging
|
||||
LOG_DIR = '/var/log/yunohost'
|
||||
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 -----------------------------------
|
||||
|
||||
|
@ -37,79 +45,112 @@ def _die(message, title='Error:'):
|
|||
print('%s %s' % (colorize(title, 'red'), message))
|
||||
sys.exit(1)
|
||||
|
||||
def _check_in_devel():
|
||||
"""Check and load if needed development environment"""
|
||||
global IN_DEVEL, LOG_DIR
|
||||
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)
|
||||
def _parse_api_args():
|
||||
"""Parse main arguments for the api"""
|
||||
import argparse
|
||||
|
||||
# Update global variables
|
||||
IN_DEVEL = True
|
||||
LOG_DIR = '%s/log' % basedir
|
||||
parser = argparse.ArgumentParser(add_help=False,
|
||||
description="Run the YunoHost API to manage your server.",
|
||||
)
|
||||
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():
|
||||
"""Parse additional arguments and return remaining ones"""
|
||||
argv = list(sys.argv)
|
||||
argv.pop(0)
|
||||
return parser.parse_args()
|
||||
|
||||
if '--no-cache' in argv:
|
||||
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():
|
||||
def _init_moulinette(use_websocket=True, debug=False, verbose=False):
|
||||
"""Configure logging and initialize the moulinette"""
|
||||
from moulinette import init
|
||||
|
||||
# Define loggers handlers
|
||||
handlers = set(LOGGERS_HANDLERS)
|
||||
if not use_websocket and 'api' in handlers:
|
||||
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
|
||||
logging = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': True,
|
||||
'formatters': {
|
||||
'simple': {
|
||||
'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s - %(message)s'
|
||||
'console': {
|
||||
'format': '%(relativeCreated)-5d %(levelname)-8s %(name)s %(funcName)s - %(fmessage)s'
|
||||
},
|
||||
'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': {
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'simple',
|
||||
'stream': 'ext://sys.stderr',
|
||||
'api': {
|
||||
'class': 'moulinette.interfaces.api.APIQueueHandler',
|
||||
},
|
||||
'file': {
|
||||
'class': 'logging.handlers.WatchedFileHandler',
|
||||
'formatter': 'precise',
|
||||
'filename': '%s/%s' % (LOG_DIR, LOG_FILE),
|
||||
'filters': ['action'],
|
||||
},
|
||||
'console': {
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'console',
|
||||
'stream': 'ext://sys.stdout',
|
||||
'filters': ['action'],
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'moulinette': {
|
||||
'level': LOGGERS_LEVEL,
|
||||
'handlers': LOGGERS_HANDLERS,
|
||||
},
|
||||
'yunohost': {
|
||||
'level': LOGGERS_LEVEL,
|
||||
'handlers': LOGGERS_HANDLERS,
|
||||
'level': level,
|
||||
'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 ----------------------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
_check_in_devel()
|
||||
_parse_argv()
|
||||
_init_moulinette()
|
||||
opts = _parse_api_args()
|
||||
_init_moulinette(opts.use_websocket, opts.debug, opts.verbose)
|
||||
|
||||
from moulinette import (api, MoulinetteError)
|
||||
# Run the server
|
||||
from moulinette import api, MoulinetteError
|
||||
from yunohost import get_versions
|
||||
try:
|
||||
# Run the server
|
||||
api(_retrieve_namespaces(), port=6787,
|
||||
ret = api(_retrieve_namespaces(),
|
||||
host=opts.host, port=opts.port,
|
||||
routes={
|
||||
('GET', '/installed'): is_installed,
|
||||
('GET', '/version'): get_versions,
|
||||
},
|
||||
use_cache=USE_CACHE, use_websocket=USE_WEBSOCKET)
|
||||
except MoulinetteError as e:
|
||||
_die(e.strerror, m18n.g('error'))
|
||||
sys.exit(0)
|
||||
use_cache=opts.use_cache, use_websocket=opts.use_websocket
|
||||
)
|
||||
sys.exit(ret)
|
||||
|
|
|
@ -291,6 +291,18 @@ domain:
|
|||
extra:
|
||||
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()
|
||||
# info:
|
||||
# action_help: Get domain informations
|
||||
|
@ -318,10 +330,10 @@ app:
|
|||
arguments:
|
||||
-u:
|
||||
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:
|
||||
full: --name
|
||||
help: Name of the list (default fapp)
|
||||
help: Name of the list (default yunohost)
|
||||
extra:
|
||||
pattern: &pattern_listname
|
||||
- !!str ^[a-z0-9_]+$
|
||||
|
@ -505,6 +517,14 @@ app:
|
|||
full: --sql
|
||||
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()
|
||||
makedefault:
|
||||
action_help: Redirect domain root to an app
|
||||
|
@ -1009,6 +1029,10 @@ firewall:
|
|||
reload:
|
||||
action_help: Reload all firewall rules
|
||||
api: PUT /firewall
|
||||
arguments:
|
||||
--skip-upnp:
|
||||
help: Do not refresh port forwarding using UPnP
|
||||
action: store_true
|
||||
|
||||
### firewall_allow()
|
||||
allow:
|
||||
|
@ -1189,9 +1213,12 @@ tools:
|
|||
### tools_maindomain()
|
||||
maindomain:
|
||||
action_help: Main domain change tool
|
||||
api: PUT /domains/main
|
||||
api:
|
||||
- GET /domains/main
|
||||
- PUT /domains/main
|
||||
configuration:
|
||||
authenticate: all
|
||||
lock: false
|
||||
arguments:
|
||||
-o:
|
||||
full: --old-domain
|
||||
|
@ -1284,6 +1311,16 @@ hook:
|
|||
app:
|
||||
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()
|
||||
list:
|
||||
action_help: List available hooks for an action
|
||||
|
@ -1320,21 +1357,20 @@ hook:
|
|||
help: Ordered list of arguments to pass to the script
|
||||
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()
|
||||
exec:
|
||||
action_help: Execute hook from a file with arguments
|
||||
api: GET /hook
|
||||
arguments:
|
||||
file:
|
||||
help: Script to execute
|
||||
path:
|
||||
help: Path of the script to execute
|
||||
-a:
|
||||
full: --args
|
||||
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
|
||||
|
|
|
@ -27,3 +27,15 @@ ynh_bind_or_cp() {
|
|||
fi
|
||||
$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
92
data/apps/helpers.d/mysql
Normal 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';"
|
||||
}
|
88
data/apps/helpers.d/package
Normal file
88
data/apps/helpers.d/package
Normal 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 $@
|
||||
}
|
|
@ -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')
|
||||
}
|
27
data/apps/helpers.d/setting
Normal file
27
data/apps/helpers.d/setting
Normal 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"
|
||||
}
|
11
data/apps/helpers.d/string
Normal file
11
data/apps/helpers.d/string
Normal 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'
|
||||
}
|
|
@ -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
|
||||
# | ret: retcode - 0 if user exists, 1 otherwise
|
||||
# | arg: username - the username to check
|
||||
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
|
||||
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
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ ynh_get_plain_key() {
|
|||
[[ "$line" =~ ^${prefix}[^#] ]] && return
|
||||
echo $line
|
||||
elif [[ "$line" =~ ^${prefix}${key}$ ]]; then
|
||||
if [[ -n "$1" ]]; then
|
||||
if [[ -n "${1:-}" ]]; then
|
||||
prefix+="#"
|
||||
key=$1
|
||||
shift
|
||||
|
|
12
data/bash-completion.d/yunohost
Normal file
12
data/bash-completion.d/yunohost
Normal 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"
|
|
@ -1,15 +1,16 @@
|
|||
backup_dir="$1/conf/ldap"
|
||||
sudo mkdir -p $backup_dir
|
||||
#!/bin/bash
|
||||
|
||||
backup_dir="${1}/conf/ldap"
|
||||
sudo mkdir -p "$backup_dir"
|
||||
|
||||
# Fix for first jessie yunohost where slapd.conf is called slapd-yuno.conf
|
||||
# without slapcat doesn't work
|
||||
if [ ! -f /etc/ldap/slapd.conf ]
|
||||
then
|
||||
sudo mv /etc/ldap/slapd-yuno.conf /etc/ldap/slapd.conf
|
||||
fi
|
||||
[[ ! -f /etc/ldap/slapd.conf ]] \
|
||||
&& sudo mv /etc/ldap/slapd-yuno.conf /etc/ldap/slapd.conf
|
||||
|
||||
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
|
||||
sudo bash -c "egrep -v '^entryCSN:' < $backup_dir/slapcat.ldif.raw > $backup_dir/slapcat.ldif"
|
||||
sudo rm -f $backup_dir/slapcat.ldif.raw
|
||||
# Back up the database
|
||||
sudo slapcat -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif"
|
||||
|
|
|
@ -24,29 +24,49 @@ cd /usr/share/yunohost/templates/slapd
|
|||
|| sudo yunohost service saferemove -s slapd \
|
||||
/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 mailserver.schema /etc/ldap/schema/mailserver.schema
|
||||
safe_copy ldap.conf /etc/ldap/ldap.conf
|
||||
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
|
||||
|
||||
# Fix some permissions
|
||||
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/slapd.d/
|
||||
|
||||
sudo slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d/
|
||||
sudo chown -R openldap:openldap /etc/ldap/slapd.d/
|
||||
# Check the slapd config file at first
|
||||
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
|
||||
|
|
|
@ -18,30 +18,17 @@ function safe_copy () {
|
|||
|
||||
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
|
||||
main_domain=$(cat /etc/yunohost/current_host)
|
||||
cat metronome.cfg.lua.sed \
|
||||
| sed "s/{{ main_domain }}/$main_domain/g" \
|
||||
| sudo tee 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
|
||||
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
|
||||
for domain in $domain_list; do
|
||||
|
@ -51,7 +38,7 @@ for domain in $domain_list; do
|
|||
cat domain.cfg.lua.sed \
|
||||
| sed "s/{{ domain }}/$domain/g" \
|
||||
| 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
|
||||
fi
|
||||
done
|
||||
|
@ -63,9 +50,8 @@ for file in /etc/metronome/conf.d/*; do
|
|||
| sed 's|.cfg.lua||')
|
||||
sanitzed_domain="$(echo $domain | sed 's/\./%2e/g')"
|
||||
[[ $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)
|
||||
|
||||
done
|
||||
|
||||
# Create domain directory
|
||||
|
|
|
@ -37,7 +37,7 @@ done
|
|||
if [ -f /etc/yunohost/installed ]; then
|
||||
|
||||
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
|
||||
for domain in $domain_list; do
|
||||
|
@ -45,7 +45,7 @@ if [ -f /etc/yunohost/installed ]; then
|
|||
cat server.conf.sed \
|
||||
| sed "s/{{ domain }}/$domain/g" \
|
||||
| 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
|
||||
|
||||
[ -f /etc/nginx/conf.d/$domain.d/yunohost_local.conf ] \
|
||||
|
|
|
@ -19,7 +19,7 @@ function safe_copy () {
|
|||
cd /usr/share/yunohost/templates/postfix
|
||||
|
||||
# Copy plain single configuration files
|
||||
files="header_check
|
||||
files="header_checks
|
||||
ldap-accounts.cf
|
||||
ldap-aliases.cf
|
||||
ldap-domains.cf
|
||||
|
|
35
data/hooks/conf_regen/22-email-legacy
Normal file
35
data/hooks/conf_regen/22-email-legacy
Normal 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
|
|
@ -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
|
|
@ -39,16 +39,14 @@ safe_copy dovecot-ldap.conf /etc/dovecot/dovecot-ldap.conf
|
|||
|
||||
|
||||
# Setup Sieve
|
||||
sudo rm -rf /etc/dovecot/global_script
|
||||
sudo mkdir -p -m 0770 /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
|
||||
sudo mkdir -p /etc/dovecot/global_script
|
||||
sudo chmod -R 770 /etc/dovecot/global_script
|
||||
|
||||
safe_copy dovecot.sieve /etc/dovecot/global_script/dovecot.sieve
|
||||
sudo chmod 660 /etc/dovecot/global_script/dovecot.sieve > /dev/null 2>&1 \
|
||||
|| safe_copy dovecot.sieve /etc/dovecot/global_script/dovecot.sieve
|
||||
sudo sievec /etc/dovecot/global_script/dovecot.sieve
|
||||
sudo chmod 660 /etc/dovecot/global_script/dovecot.svbin
|
||||
|
||||
sudo chown -R vmail:mail /etc/dovecot/global_script
|
||||
|
||||
sudo service dovecot restart
|
||||
|
|
43
data/hooks/conf_regen/28-rmilter
Normal file
43
data/hooks/conf_regen/28-rmilter
Normal 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
|
|
@ -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
|
|
@ -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
|
28
data/hooks/conf_regen/31-rspamd
Normal file
28
data/hooks/conf_regen/31-rspamd
Normal 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
|
|
@ -21,12 +21,12 @@ function randpass () {
|
|||
|
||||
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
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
mysql_password=$(randpass 10 0)
|
||||
|
|
|
@ -15,6 +15,6 @@ function safe_copy () {
|
|||
|
||||
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
|
||||
fi
|
||||
|
|
|
@ -15,6 +15,6 @@ function safe_copy () {
|
|||
|
||||
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
|
||||
fi
|
||||
|
|
|
@ -16,7 +16,7 @@ function safe_copy () {
|
|||
cd /usr/share/yunohost/templates/dnsmasq
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
domain_list=$(sudo yunohost domain list --plain)
|
||||
domain_list=$(sudo yunohost domain list --output-as plain)
|
||||
|
||||
# Copy a configuration file for each YunoHost domain
|
||||
for domain in $domain_list; do
|
||||
|
|
|
@ -15,6 +15,6 @@ function safe_copy () {
|
|||
|
||||
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
|
||||
fi
|
||||
|
|
|
@ -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
|
|
@ -24,6 +24,6 @@ version=$(sed 's/\..*//' /etc/debian_version)
|
|||
&& sudo cp jail-jessie.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
|
||||
fi
|
||||
|
|
|
@ -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
|
||||
# service will be shut down during the operation (and sudo
|
||||
# won't be available)
|
||||
sudo bash $(pwd)/$0 $1 sudoed
|
||||
sudo /bin/bash $(readlink -f $0) $1
|
||||
|
||||
else
|
||||
service slapd stop
|
||||
|
||||
# Backup old configuration
|
||||
mv /var/lib/ldap /var/lib/ldap.old
|
||||
service slapd stop || true
|
||||
|
||||
# Recreate new DB folder
|
||||
mkdir /var/lib/ldap
|
||||
chown openldap: /var/lib/ldap
|
||||
chmod go-rwx /var/lib/ldap
|
||||
# Create a directory for backup
|
||||
TMPDIR="/tmp/$(date +%s)"
|
||||
mkdir -p "$TMPDIR"
|
||||
|
||||
# Restore LDAP configuration (just to be sure)
|
||||
cp -a $backup_dir/slapd.conf /etc/ldap/slapd.conf
|
||||
die() {
|
||||
state=$1
|
||||
error=$2
|
||||
|
||||
# Regenerate the configuration
|
||||
rm -rf /etc/ldap/slapd.d/*
|
||||
slaptest -f /etc/ldap/slapd.conf -F /etc/ldap/slapd.d
|
||||
chown -R openldap:openldap /etc/ldap/slapd.d
|
||||
cp -rfp /var/lib/ldap.old/DB_CONFIG /var/lib/ldap
|
||||
# Restore saved configuration and database
|
||||
[[ $state -ge 1 ]] \
|
||||
&& (rm -rf /etc/ldap/slapd.d &&
|
||||
mv "${TMPDIR}/slapd.d" /etc/ldap/slapd.d)
|
||||
[[ $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
|
||||
slapadd -l $backup_dir/slapcat.ldif
|
||||
service slapd start
|
||||
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
|
||||
rm -rf /var/lib/ldap.old
|
||||
rm -rf "$TMPDIR"
|
||||
fi
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -57,12 +57,12 @@ plugin {
|
|||
antispam_debug_target = syslog
|
||||
antispam_verbose_debug = 0
|
||||
antispam_backend = pipe
|
||||
antispam_spam = Junk;SPAM
|
||||
antispam_trash = Trash
|
||||
antispam_spam = SPAM;Junk
|
||||
antispam_allow_append_to_spam = no
|
||||
antispam_pipe_program = /usr/bin/sa-learn-pipe.sh
|
||||
antispam_pipe_program_spam_arg = --spam
|
||||
antispam_pipe_program_notspam_arg = --ham
|
||||
antispam_pipe_program = /usr/bin/rspamc
|
||||
antispam_pipe_program_args = -h;localhost:11334;-P;q1
|
||||
antispam_pipe_program_spam_arg = learn_spam
|
||||
antispam_pipe_program_notspam_arg = learn_ham
|
||||
}
|
||||
|
||||
plugin {
|
||||
|
|
|
@ -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;
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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);
|
|
@ -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);
|
|
@ -36,6 +36,9 @@ read_rnd_buffer_size = 256K
|
|||
net_buffer_length = 2K
|
||||
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,
|
||||
# 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.
|
||||
|
|
|
@ -2,4 +2,10 @@ location /yunohost/admin {
|
|||
alias /usr/share/yunohost/admin/;
|
||||
default_type text/html;
|
||||
index index.html;
|
||||
|
||||
# Short cache on handlebars templates
|
||||
location ~* \.(?:ms)$ {
|
||||
expires 5m;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -15,24 +15,11 @@ base dc=yunohost,dc=org
|
|||
# The LDAP protocol version to use.
|
||||
#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.
|
||||
#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
|
||||
|
|
|
@ -86,9 +86,6 @@ smtpd_sasl_security_options = noanonymous
|
|||
smtpd_sasl_local_domain =
|
||||
|
||||
|
||||
# Use AMaVis
|
||||
content_filter = amavis:[127.0.0.1]:10024
|
||||
|
||||
# Wait until the RCPT TO command before evaluating restrictions
|
||||
smtpd_delay_reject = yes
|
||||
|
||||
|
@ -128,13 +125,8 @@ smtpd_recipient_restrictions =
|
|||
reject_non_fqdn_recipient,
|
||||
reject_unknown_recipient_domain,
|
||||
reject_unauth_destination,
|
||||
check_policy_service unix:private/policy-spf
|
||||
check_policy_service inet:127.0.0.1:10023
|
||||
permit
|
||||
|
||||
# Use SPF
|
||||
policy-spf_time_limit = 3600s
|
||||
|
||||
# SRS
|
||||
sender_canonical_maps = regexp:/etc/postfix/sender_canonical
|
||||
sender_canonical_classes = envelope_sender
|
||||
|
@ -143,3 +135,11 @@ sender_canonical_classes = envelope_sender
|
|||
smtp_header_checks = regexp:/etc/postfix/header_checks
|
||||
|
||||
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
|
||||
|
|
|
@ -116,32 +116,3 @@ dovecot unix - n n - - pipe
|
|||
# (yes) (yes) (yes) (never) (100)
|
||||
# ==========================================================================
|
||||
# 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=
|
||||
|
|
|
@ -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"
|
18
data/templates/rmilter/rmilter.conf
Normal file
18
data/templates/rmilter/rmilter.conf
Normal 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";
|
||||
};
|
||||
};
|
5
data/templates/rmilter/rmilter.socket
Normal file
5
data/templates/rmilter/rmilter.socket
Normal file
|
@ -0,0 +1,5 @@
|
|||
.include /lib/systemd/system/rmilter.socket
|
||||
|
||||
[Socket]
|
||||
ListenStream=
|
||||
ListenStream=127.0.0.1:11000
|
1163
data/templates/rspamd/metrics.conf
Normal file
1163
data/templates/rspamd/metrics.conf
Normal file
File diff suppressed because it is too large
Load diff
4
data/templates/rspamd/rspamd.sieve
Normal file
4
data/templates/rspamd/rspamd.sieve
Normal file
|
@ -0,0 +1,4 @@
|
|||
require ["fileinto"];
|
||||
if header :is "X-Spam" "yes" {
|
||||
fileinto "Junk";
|
||||
}
|
|
@ -22,14 +22,15 @@ pidfile /var/run/slapd/slapd.pid
|
|||
# List of arguments that were passed to the server
|
||||
argsfile /var/run/slapd/slapd.args
|
||||
|
||||
password-hash {SSHA}
|
||||
|
||||
# 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
|
||||
modulepath /usr/lib/ldap
|
||||
moduleload back_hdb
|
||||
moduleload back_mdb
|
||||
moduleload memberof
|
||||
|
||||
# The maximum number of entries that is returned for a search operation
|
||||
|
@ -40,52 +41,31 @@ sizelimit 500
|
|||
tool-threads 1
|
||||
|
||||
#######################################################################
|
||||
# Specific Backend Directives for hdb:
|
||||
# Specific Backend Directives for mdb:
|
||||
# Backend specific directives apply to this backend until another
|
||||
# 'backend' directive occurs
|
||||
backend hdb
|
||||
backend mdb
|
||||
|
||||
#######################################################################
|
||||
# Specific Backend Directives for 'other':
|
||||
# Backend specific directives apply to this backend until another
|
||||
# 'backend' directive occurs
|
||||
#backend <other>
|
||||
|
||||
#######################################################################
|
||||
# Specific Directives for database #1, of type hdb:
|
||||
# Specific Directives for database #1, of type mdb:
|
||||
# Database specific directives apply to this databasse until another
|
||||
# 'database' directive occurs
|
||||
database hdb
|
||||
database mdb
|
||||
|
||||
# The base of your directory in database #1
|
||||
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"
|
||||
|
||||
# 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
|
||||
index objectClass eq
|
||||
index uid eq,sub
|
||||
index entryCSN,entryUUID eq
|
||||
index objectClass eq
|
||||
index uid eq,sub
|
||||
index entryCSN,entryUUID eq
|
||||
|
||||
# Save the time that the entry gets modified, for database #1
|
||||
lastmod on
|
||||
|
@ -94,26 +74,25 @@ lastmod on
|
|||
# failure and to speed slapd shutdown.
|
||||
checkpoint 512 30
|
||||
|
||||
# Where to store the replica logs for database #1
|
||||
# replogfile /var/lib/ldap/replog
|
||||
|
||||
# The userPassword by default can be changed
|
||||
# by the entry owning it if they are authenticated.
|
||||
# Others should not be able to see it, except the
|
||||
# admin entry below
|
||||
# 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 anonymous auth
|
||||
by anonymous auth
|
||||
by self write
|
||||
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
|
||||
by dn="cn=admin,dc=yunohost,dc=org" write
|
||||
by self write
|
||||
by * read
|
||||
|
||||
|
||||
# Ensure read access to the base for things like
|
||||
# supportedSASLMechanisms. Without this you may
|
||||
# have problems with SASL not knowing what
|
||||
|
@ -129,14 +108,5 @@ access to dn.base="" by * read
|
|||
# can read everything.
|
||||
access to *
|
||||
by dn="cn=admin,dc=yunohost,dc=org" write
|
||||
by group/groupOfNames/Member="cn=admin,ou=groups,dc=yunohost,dc=org" write
|
||||
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"
|
||||
by group/groupOfNames/Member="cn=admin,ou=groups,dc=yunohost,dc=org" write
|
||||
by * read
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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" }
|
||||
}
|
|
@ -13,6 +13,10 @@ dovecot:
|
|||
postfix:
|
||||
status: service
|
||||
log: [/var/log/mail.log,/var/log/mail.err]
|
||||
rmilter:
|
||||
status: systemctl status rmilter.socket
|
||||
rspamd:
|
||||
status: systemctl status rspamd.socket
|
||||
mysql:
|
||||
status: service
|
||||
log: [/var/log/mysql.log,/var/log/mysql.err]
|
||||
|
@ -32,11 +36,10 @@ php5-fpm:
|
|||
log: /var/log/php5-fpm.log
|
||||
yunohost-api:
|
||||
status: service
|
||||
log: /var/log/yunohost.log
|
||||
postgrey:
|
||||
log: /var/log/yunohost/yunohost-api.log
|
||||
yunohost-firewall:
|
||||
status: service
|
||||
log: /var/log/mail.log
|
||||
amavis:
|
||||
postgrey:
|
||||
status: service
|
||||
log: /var/log/mail.log
|
||||
nslcd:
|
||||
|
@ -44,8 +47,5 @@ nslcd:
|
|||
log: /var/log/syslog
|
||||
nsswitch:
|
||||
status: service
|
||||
spamassassin:
|
||||
status: service
|
||||
log: /var/log/mail.log
|
||||
udisks-glue:
|
||||
udisks2:
|
||||
status: service
|
||||
|
|
172
debian/changelog
vendored
172
debian/changelog
vendored
|
@ -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
|
||||
|
||||
[ Julien Malik ]
|
||||
|
|
6
debian/conf/pam/mkhomedir
vendored
Normal file
6
debian/conf/pam/mkhomedir
vendored
Normal 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
29
debian/control
vendored
|
@ -2,14 +2,15 @@ Source: yunohost
|
|||
Section: utils
|
||||
Priority: extra
|
||||
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
|
||||
X-Python-Version: >= 2.7
|
||||
Homepage: https://yunohost.org/
|
||||
|
||||
Package: yunohost
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, ${shlibs:Depends},
|
||||
moulinette (>= 2.2.1),
|
||||
Depends: ${python:Depends}, ${misc:Depends},
|
||||
moulinette (>= 2.3.4),
|
||||
python-psutil,
|
||||
python-requests,
|
||||
glances,
|
||||
|
@ -27,18 +28,20 @@ Depends: ${misc:Depends}, ${shlibs:Depends},
|
|||
curl,
|
||||
mariadb-server | mysql-server, php5-mysql | php5-mysqlnd,
|
||||
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,
|
||||
amavisd-new, razor, pyzor, dovecot-antispam, spamassassin, fail2ban,
|
||||
dovecot-antispam, fail2ban,
|
||||
nginx-extras (>=1.6.2), php5-fpm, php5-ldap, php5-intl,
|
||||
dnsmasq, openssl, avahi-daemon,
|
||||
ssowat, metronome
|
||||
ssowat, metronome,
|
||||
rspamd, rmilter (>=1.7.0), redis-server, opendkim-tools
|
||||
Recommends: yunohost-admin,
|
||||
bash-completion, rsyslog, ntp, openssh-server,
|
||||
inetutils-ping | iputils-ping,
|
||||
php5-gd, php5-curl, php-gettext, php5-mcrypt,
|
||||
udisks-glue, unattended-upgrades,
|
||||
unattended-upgrades,
|
||||
libdbd-ldap-perl, libnet-dns-perl
|
||||
Suggests: htop, vim, rsync, acpi-support-base
|
||||
Suggests: htop, vim, rsync, acpi-support-base, udisks2
|
||||
Conflicts: iptables-persistent,
|
||||
moulinette-yunohost, yunohost-config,
|
||||
yunohost-config-others, yunohost-config-postfix,
|
||||
|
@ -50,9 +53,11 @@ Replaces: moulinette-yunohost, yunohost-config,
|
|||
yunohost-config-dovecot, yunohost-config-slapd,
|
||||
yunohost-config-nginx, yunohost-config-amavis,
|
||||
yunohost-config-mysql, yunohost-predepends
|
||||
Description: YunoHost installation package
|
||||
YunoHost aims to make self-hosting accessible to everyone.
|
||||
Description: manageable and configured self-hosting server
|
||||
YunoHost aims to make self-hosting accessible to everyone. It configures
|
||||
an email, Web and IM server alongside a LDAP base. It also provides
|
||||
facilities to manage users, domains, apps and so.
|
||||
.
|
||||
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
|
||||
an API.
|
||||
moulinette. It allows one to manage the server with a command-line tool
|
||||
and an API.
|
||||
|
|
5
debian/install
vendored
5
debian/install
vendored
|
@ -1,8 +1,11 @@
|
|||
bin/* /usr/bin/
|
||||
data/bash-completion.d/yunohost /etc/bash_completion.d/
|
||||
data/actionsmap/* /usr/share/moulinette/actionsmap/
|
||||
data/hooks/* /usr/share/yunohost/hooks/
|
||||
data/other/* /usr/share/yunohost/yunohost-config/moulinette/
|
||||
data/templates/* /usr/share/yunohost/templates/
|
||||
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/
|
||||
src/yunohost/*.py /usr/lib/moulinette/yunohost/
|
||||
|
|
8
debian/logrotate
vendored
Normal file
8
debian/logrotate
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
/var/log/yunohost/*.log {
|
||||
weekly
|
||||
rotate 4
|
||||
delaycompress
|
||||
compress
|
||||
notifempty
|
||||
missingok
|
||||
}
|
29
debian/postinst
vendored
29
debian/postinst
vendored
|
@ -4,16 +4,25 @@ set -e
|
|||
|
||||
do_configure() {
|
||||
rm -rf /var/cache/moulinette/*
|
||||
service yunohost-api restart
|
||||
|
||||
if [ ! -f /etc/yunohost/installed ]; then
|
||||
bash /usr/share/yunohost/hooks/conf_regen/01-yunohost
|
||||
bash /usr/share/yunohost/hooks/conf_regen/02-ssl
|
||||
bash /usr/share/yunohost/hooks/conf_regen/06-slapd
|
||||
bash /usr/share/yunohost/hooks/conf_regen/15-nginx
|
||||
bash /usr/share/yunohost/hooks/conf_regen/01-yunohost True
|
||||
bash /usr/share/yunohost/hooks/conf_regen/02-ssl True
|
||||
bash /usr/share/yunohost/hooks/conf_regen/06-slapd True
|
||||
bash /usr/share/yunohost/hooks/conf_regen/15-nginx True
|
||||
else
|
||||
echo "Regenerating configuration, this might take a while..."
|
||||
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
|
||||
|
||||
# update PAM configs
|
||||
pam-auth-update --package
|
||||
}
|
||||
|
||||
# summary of how this script can be called:
|
||||
|
@ -38,3 +47,13 @@ case "$1" in
|
|||
exit 1
|
||||
;;
|
||||
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
11
debian/prerm
vendored
Normal 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
12
debian/rules
vendored
|
@ -5,8 +5,14 @@
|
|||
#export DH_VERBOSE=1
|
||||
|
||||
%:
|
||||
dh ${@} --with=systemd
|
||||
dh ${@} --with=python2,systemd
|
||||
|
||||
override_dh_installinit:
|
||||
dh_installinit --name=yunohost-api
|
||||
dh_installinit --name=yunohost-firewall
|
||||
dh_installinit --noscripts
|
||||
|
||||
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
4
debian/yunohost-api.default
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Override yunohost-api options.
|
||||
# Example to log debug: DAEMON_OPTS="--debug"
|
||||
#
|
||||
#DAEMON_OPTS=""
|
7
debian/yunohost-api.init
vendored
7
debian/yunohost-api.init
vendored
|
@ -21,6 +21,11 @@ PIDFILE=/var/run/$NAME.pid
|
|||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
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
|
||||
[ -x "$DAEMON" ] || exit 0
|
||||
|
||||
|
@ -45,7 +50,7 @@ do_start()
|
|||
|| return 1
|
||||
start-stop-daemon --start --background --make-pidfile --quiet --no-close \
|
||||
--pidfile $PIDFILE --exec $DAEMON -- \
|
||||
$DAEMON_ARGS >>$LOGFILE 2>&1 \
|
||||
$DAEMON_OPTS >>$LOGFILE 2>&1 \
|
||||
|| return 2
|
||||
}
|
||||
|
||||
|
|
4
debian/yunohost-api.service
vendored
4
debian/yunohost-api.service
vendored
|
@ -4,7 +4,9 @@ After=network.target
|
|||
|
||||
[Service]
|
||||
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
|
||||
Restart=always
|
||||
RestartSec=1
|
||||
|
|
11
debian/yunohost-firewall.service
vendored
Normal file
11
debian/yunohost-firewall.service
vendored
Normal 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
|
89
lib/metronome/modules/mod_auth_ldap2.lua
Normal file
89
lib/metronome/modules/mod_auth_ldap2.lua
Normal 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));
|
243
lib/metronome/modules/mod_storage_ldap.lua
Normal file
243
lib/metronome/modules/mod_storage_ldap.lua
Normal 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);
|
110
locales/de.json
110
locales/de.json
|
@ -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_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_install_failed": "Diese App ist auf diesem Ort nicht Installbar",
|
||||
"app_extraction_failed": "Installationsdateien nicht extrahierbar",
|
||||
"app_install_files_invalid": "Ungültige Installationsdateien",
|
||||
"app_no_upgrade": "Keine App zu updaten",
|
||||
"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",
|
||||
"ssowat_conf_updated": "SSOwat beständige Einstellung erfolgreich upgedatet",
|
||||
"ssowat_conf_generated": "SSOwat-einstellung erfolgreich erzeugt",
|
||||
"mysql_db_creation_failed": "Fehler beim MySQL-datenbankerzeugung",
|
||||
"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",
|
||||
"app_unknown": "unbekannte App",
|
||||
"app_upgraded": "{:s} erfolgreich updaten",
|
||||
"custom_app_url_required": "Bitte eine URL geben, um deine nüzterspezifische App {:s} zu updaten",
|
||||
"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_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_deletion_failed": "Domain nicht löschbar",
|
||||
"domain_creation_failed": "Domain nicht erzeugbar",
|
||||
"domain_deleted": "Domain erfolgreich gelöscht",
|
||||
"no_internet_connection": "Server not connected to the Internet",
|
||||
"dyndns_key_generating": "DNS key is being generated, it may take a while...",
|
||||
"dyndns_unavailable": "DynDNS-subdomain nicht verfügbar",
|
||||
"dyndns_registration_failed": "DynDNS-domain {:s} nicht registrierbar",
|
||||
"dyndns_registered": "DynDNS-domain erfolgreich registriert",
|
||||
"dyndns_ip_update_failed": "IP-adress auf DynDNS nicht updatbar",
|
||||
"dyndns_ip_updated": "IP-adress auf DynDNS erfolgreich upgedatet",
|
||||
"domain_deletion_failed": "Domain nicht löschbar",
|
||||
"domain_dyndns_already_subscribed": "Du hast dich schon für einen DynDNS-domain angemeldet",
|
||||
"domain_dyndns_invalid": "Domain mit DynDNS nicht nützbar",
|
||||
"domain_dyndns_root_unknown": "Unbekannte DynDNS-hauptdomain",
|
||||
"domain_exists": "diese Domain existiert schon",
|
||||
"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_unknown": "Unbekannte Domain",
|
||||
"done": "Erledigt.",
|
||||
"downloading": "Herunterladend...",
|
||||
"dyndns_cron_installed": "DynDNS cron job erfolgreich installiert",
|
||||
"dyndns_cron_remove_failed": "DynDNS cron job nicht löschbar",
|
||||
"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",
|
||||
"hook_choice_invalid": "ungültige Wahl '{:s}'",
|
||||
"hook_argument_missing": "Fehlend Argument '{:s}'",
|
||||
"mountpoint_unknown": "unbekannten Einhängepunkt",
|
||||
"unit_unknown": "unbekannte Einheit '{:s}'",
|
||||
"monitor_period_invalid": "Falschen Zeitraum",
|
||||
"monitor_stats_no_update": "Keine Monitoringstatistik zu updaten",
|
||||
"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",
|
||||
"hook_choice_invalid": "ungültige Wahl '{:s}'",
|
||||
"installation_complete": "erfolgreich installiert",
|
||||
"installation_failed": "Fehler beim Installation",
|
||||
"iptables_unavailable": "Du kannst nicht hier die IP-Tabelle bearbeiten. Entweder bist du in einen Container oder deinen Systemkern erhält es nicht.",
|
||||
"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",
|
||||
"service_unknown": "Unbekannte Dienst '{:s}'",
|
||||
"service_start_failed": "Kann nicht '{:s}' -dienst starten",
|
||||
"monitor_not_enabled": "Servermonitoring ist nicht aktiviert",
|
||||
"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_started": "'{:s}' -dienst erfolgreich gestartet",
|
||||
"service_stop_failed": "Kann nicht '{:s}' -dienst stoppen",
|
||||
"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_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."
|
||||
}
|
||||
|
|
|
@ -16,19 +16,23 @@
|
|||
"appslist_removed" : "Apps list successfully removed",
|
||||
"app_unknown" : "Unknown app",
|
||||
"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}",
|
||||
"app_recent_version_required" : "{:s} requires a more recent version of the moulinette",
|
||||
"app_upgraded" : "{:s} successfully upgraded",
|
||||
"app_recent_version_required" : "{app:s} requires a more recent version of YunoHost",
|
||||
"app_upgraded" : "{app:s} successfully upgraded",
|
||||
"app_upgrade_failed" : "Unable to upgrade all apps",
|
||||
"app_id_invalid" : "Invalid app id",
|
||||
"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_install_failed" : "Unable to install the app on this location",
|
||||
"app_extraction_failed" : "Unable to extract installation files",
|
||||
"app_install_files_invalid" : "Invalid installation files",
|
||||
"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",
|
||||
"ssowat_conf_updated" : "SSOwat persistent configuration successfully updated",
|
||||
"ssowat_conf_generated" : "SSOwat configuration successfully generated",
|
||||
|
@ -37,7 +41,8 @@
|
|||
"mysql_db_initialized" : "MySQL database successfully initialized",
|
||||
"extracting" : "Extracting...",
|
||||
"downloading" : "Downloading...",
|
||||
"executing_script": "Executing script...",
|
||||
"executing_script": "Executing script '{script:s}'...",
|
||||
"executing_command": "Executing command '{command:s}'...",
|
||||
"done" : "Done.",
|
||||
|
||||
"path_removal_failed" : "Unable to remove path {:s}",
|
||||
|
@ -84,8 +89,8 @@
|
|||
|
||||
"hook_list_by_invalid" : "Invalid property to list hook by",
|
||||
"hook_name_unknown" : "Unknown hook name '{:s}'",
|
||||
"hook_choice_invalid" : "Invalid choice '{:s}'",
|
||||
"hook_argument_missing" : "Missing argument '{:s}'",
|
||||
"hook_exec_failed" : "Script execution failed",
|
||||
"hook_exec_not_terminated" : "Script execution hasn’t terminated",
|
||||
|
||||
"mountpoint_unknown" : "Unknown mountpoint",
|
||||
"unit_unknown" : "Unknown unit '{:s}'",
|
||||
|
@ -116,6 +121,11 @@
|
|||
"service_status_failed" : "Unable to determine status of service '{:s}'",
|
||||
"service_no_log" : "No log to display for service '{:s}'",
|
||||
"service_cmd_exec_failed" : "Unable to execute command '{:s}'",
|
||||
"services_configured": "Configuration successfully generated",
|
||||
"service_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_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_forbidden" : "Forbidden output directory",
|
||||
"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_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_extracting_archive" : "Extracting 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_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_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_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_hook_unavailable" : "Restauration hook '{hook:s}' not available on your system",
|
||||
"restore_app_failed" : "Unable to restore the app '{app:s}'",
|
||||
"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_nothings_done" : "Nothing has been restored",
|
||||
"restore_cleaning_failed" : "Unable to clean restoration temporary directory",
|
||||
"restore_complete" : "Restore complete",
|
||||
"restore_already_installed_app": "An app is already installed with the id '{app:s}'",
|
||||
"unbackup_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}'",
|
||||
"unbackup_app" : "App '{app:s}' will not be saved",
|
||||
"no_restore_script": "No restore script found for the app '{app:s}'",
|
||||
"unrestore_app" : "App '{:s}' will not be restored",
|
||||
"backup_delete_error" : "Unable to delete '{:s}'",
|
||||
"unrestore_app" : "App '{app:s}' will not be restored",
|
||||
"backup_delete_error" : "Unable to delete '{path:s}'",
|
||||
"backup_deleted" : "Backup successfully deleted",
|
||||
|
||||
"field_invalid" : "Invalid field '{:s}'",
|
||||
|
|
292
locales/es.json
292
locales/es.json
|
@ -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}'",
|
||||
"license_undefined": "indefinido",
|
||||
"no_appslist_found": "No se encontró ninguna lista de Apps",
|
||||
"custom_appslist_name_required": "Debe proporcionar un nombre para la lista de aplicaciones personalizadas ",
|
||||
"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 ",
|
||||
"admin_password": "Contraseña administrativa",
|
||||
"admin_password_change_failed": "No se pudo cambiar la contraseña",
|
||||
"admin_password_changed": "Contraseña administrativa se cambió con éxito",
|
||||
"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_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_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",
|
||||
"ssowat_conf_updated": "Configuración persistente SSOwat actualizada con éxito",
|
||||
"ssowat_conf_generated": "Configuración SSOwat generado con éxito ",
|
||||
"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",
|
||||
"extracting": "Extrayendo...",
|
||||
"downloading": "Descargando...",
|
||||
"executing_script": "Ejecutando script...",
|
||||
"done": "Completo.",
|
||||
"path_removal_failed": "No se pudo quitar la ruta {:s}",
|
||||
"domain_unknown": "Dominio desconocido",
|
||||
"domain_dyndns_invalid": "El dominio no es valido para usar con DynDNS",
|
||||
"domain_dyndns_already_subscribed": "Ya te has suscrito a un dominio DynDNS.",
|
||||
"domain_dyndns_root_unknown": "Dominio raíz DynDNS desconocido ",
|
||||
"domain_cert_gen_failed": "No se pudo crear certificado",
|
||||
"domain_exists": "El dominio ya existe",
|
||||
"app_unknown": "App desconocida",
|
||||
"app_upgrade_failed": "No se pudo actualizar todas las aplicaciones ",
|
||||
"app_upgraded": "{:s} actualizado con éxito",
|
||||
"appslist_fetched": "Lista de aplicaciones se trajo con éxito",
|
||||
"appslist_removed": "Lista de aplicaciones se eliminó con éxito",
|
||||
"appslist_retrieve_error": "No se pudo recuperar la lista de aplicaciones a distancia ",
|
||||
"appslist_unknown": "Lista de aplicaciones desconocidas",
|
||||
"ask_current_admin_password": "Contraseña administrativa presente",
|
||||
"ask_email": "Correo electrónico",
|
||||
"ask_firstname": "Nombre",
|
||||
"ask_lastname": "Apellido",
|
||||
"ask_list_to_remove": "Lista a quitar",
|
||||
"ask_main_domain": "Dominio principal",
|
||||
"ask_new_admin_password": "Contraseña administrativa nueva",
|
||||
"ask_password": "Contraseña",
|
||||
"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'",
|
||||
"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_not_found": "Archivo de zonas DNS por el dominio [:s] no estaba encontrado",
|
||||
"domain_creation_failed": "No se pudo crear el dominio",
|
||||
"domain_created": "Dominio creado con éxito.",
|
||||
"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",
|
||||
"done": "Completo.",
|
||||
"downloading": "Descargando...",
|
||||
"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_removed": "Trabajo cron DynDNS se quitó con éxito",
|
||||
"port_available": "El puerto {} está disponible",
|
||||
"port_unavailable": "El puerto {} no está disponible",
|
||||
"port_already_opened": "El puerto {} ya está abierto por {:s} connecciones",
|
||||
"port_already_closed": "El puerto {} ya está cerrado por {:s} connecciones.",
|
||||
"iptables_unavailable": "No puedes modificar los iptables aquí. Eres en un contenedor o su kernel no soporte este opción.",
|
||||
"ip6tables_unavailable": "No puedes modificar los ip6tables aquí. Eres en un contenedor o su kernel no soporte este opción.",
|
||||
"upnp_dev_not_found": "No se encontró ninguno dispositivo UPnP ",
|
||||
"upnp_port_open_failed": "No se pudo abrir puertos por UPnP",
|
||||
"upnp_enabled": "UPnP activado con éxito",
|
||||
"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.",
|
||||
"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_key_generating": "Generación del llave de DNS está en curso. Este podría durar unos momentos...",
|
||||
"dyndns_registered": "El dominio DynDNS era registrado con éxito.",
|
||||
"dyndns_registration_failed": "No se pudo registrar el dominio DynDNS: {:s}",
|
||||
"dyndns_unavailable": "Subdominio DynDNS no disponible",
|
||||
"executing_script": "Ejecutando script...",
|
||||
"extracting": "Extrayendo...",
|
||||
"field_invalid": "Campo inválido '{:s}'",
|
||||
"firewall_reload_failed": "No se pudo recargar el cortafuegos",
|
||||
"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_name_unknown": "Hook desconocido '{:s}'",
|
||||
"hook_choice_invalid": "Selección inválida '{:s}'",
|
||||
"hook_argument_missing": "Falta un parámetro '{:s}'",
|
||||
"mountpoint_unknown": "Punto de montaje desconocido",
|
||||
"unit_unknown": "Unidad '{:s}' desconocido",
|
||||
"monitor_period_invalid": "Período de tiempo inválido",
|
||||
"monitor_stats_no_update": "No hay ninguna estadísticos de la supervisión del sistema a realizar",
|
||||
"monitor_stats_file_not_found": "No se pudo encontrar el archivo de estadísticos",
|
||||
"monitor_stats_period_unavailable": "No hay estadísticos del período del tiempo",
|
||||
"monitor_enabled": "Supervisión del sistema activado con éxito",
|
||||
"installation_complete": "La instalación se ha completado",
|
||||
"installation_failed": "La Instalación se ha fracasado",
|
||||
"ip6tables_unavailable": "No puedes modificar los ip6tables aquí. Eres en un contenedor o su kernel no soporte este opción.",
|
||||
"iptables_unavailable": "No puedes modificar los iptables aquí. Eres en un contenedor o su kernel no soporte este opción.",
|
||||
"ldap_initialized": "LDAP se inició con éxito",
|
||||
"license_undefined": "indefinido",
|
||||
"mail_alias_remove_failed": "No se pudo quitar el alias de correos '{:s}'",
|
||||
"mail_domain_unknown": "El dominio de correos '{:s}' es desconocido",
|
||||
"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_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",
|
||||
"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_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_removed": "Servicio quitado con éxito",
|
||||
"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_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_no_log": "No hay archivo historial del servicio '{:s}' a exhibir",
|
||||
"service_cmd_exec_failed": "No se pudo ejecutar comando '{:s}'",
|
||||
"ldap_initialized": "LDAP se inició con éxito",
|
||||
"admin_password_change_failed": "No se pudo cambiar la contraseña",
|
||||
"admin_password_changed": "Contraseña administrativa se cambió con éxito",
|
||||
"new_domain_required": "Debe proporcionar el dominio principal nuevo",
|
||||
"maindomain_change_failed": "No se pudo cambiar el dominio principal",
|
||||
"maindomain_changed": "Dominio principal se cambió con éxito",
|
||||
"yunohost_installing": "Instalando YunoHost...",
|
||||
"service_stop_failed": "No se pudo parar el servicio '{:s}'",
|
||||
"service_stopped": "Servicio '{:s}' parado con éxito",
|
||||
"service_unknown": "Servicio desconocido '{:s}'",
|
||||
"ssowat_conf_generated": "Configuración SSOwat generado con éxito ",
|
||||
"ssowat_conf_updated": "Configuración persistente SSOwat actualizada con éxito",
|
||||
"system_upgraded": "Actualización del sistema se ha completado con éxito.",
|
||||
"system_username_exists": "Nombre de usuario ya existe en los usuarios del sistema",
|
||||
"unbackup_app": "La App '{:s}' no será guardada",
|
||||
"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_ca_creation_failed": "No se pudo crear un autoridad de certificación nuevo",
|
||||
"yunohost_configured": "YunoHost se configuró con éxito.",
|
||||
"updating_apt_cache": "Actualizando la lista de paquetes disponibles...",
|
||||
"update_cache_failed": "No se pudo actualizar el cache APT",
|
||||
"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 -_."
|
||||
"yunohost_installing": "Instalando YunoHost...",
|
||||
"yunohost_not_installed": "YunoHost no está instalado o la instilación ha cumplido con errores. Por favor, ejecute 'yunohost tools postinstall'."
|
||||
}
|
||||
|
|
303
locales/fr.json
303
locales/fr.json
|
@ -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",
|
||||
"license_undefined": "indéfinie",
|
||||
"no_appslist_found": "Aucune liste d'applications trouvée",
|
||||
"custom_appslist_name_required": "Vous devez spécifier un nom pour votre liste d'applications personnalisée",
|
||||
"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",
|
||||
"admin_password": "Mot de passe d'administration",
|
||||
"admin_password_change_failed": "Impossible de modifier le mot de passe d'administration",
|
||||
"admin_password_changed": "Mot de passe d'administration modifié avec succès",
|
||||
"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_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_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",
|
||||
"ssowat_conf_updated": "Configuration persistante de SSOwat mise à jour avec succès",
|
||||
"ssowat_conf_generated": "Configuration de SSOwat générée avec succès",
|
||||
"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",
|
||||
"extracting": "Extraction...",
|
||||
"downloading": "Téléchargement...",
|
||||
"executing_script": "Exécution du script...",
|
||||
"done": "Terminé.",
|
||||
"path_removal_failed": "Impossible de supprimer le chemin {:s}",
|
||||
"domain_unknown": "Domaine inconnu",
|
||||
"domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS",
|
||||
"domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS",
|
||||
"domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu",
|
||||
"domain_cert_gen_failed": "Impossible de générer le certificat",
|
||||
"domain_exists": "Le domaine existe déjà",
|
||||
"app_unknown": "Application inconnue",
|
||||
"app_upgrade_failed": "Impossible de mettre à jour toutes les applications",
|
||||
"app_upgraded": "{:s} mis à jour avec succès",
|
||||
"appslist_fetched": "Liste d'applications récupérée avec succès",
|
||||
"appslist_removed": "Liste d'applications supprimée avec succès",
|
||||
"appslist_retrieve_error": "Impossible de récupérer la liste d'applications distante",
|
||||
"appslist_unknown": "Liste d'applications inconnue",
|
||||
"ask_current_admin_password": "Mot de passe d'administration actuel",
|
||||
"ask_email": "Adresse mail",
|
||||
"ask_firstname": "Prénom",
|
||||
"ask_lastname": "Nom",
|
||||
"ask_list_to_remove": "Liste à supprimer",
|
||||
"ask_main_domain": "Domaine principal",
|
||||
"ask_new_admin_password": "Nouveau mot de passe d'administration",
|
||||
"ask_password": "Mot de passe",
|
||||
"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'",
|
||||
"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_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}",
|
||||
"domain_creation_failed": "Impossible de créer le domaine",
|
||||
"domain_created": "Domaine créé avec succès",
|
||||
"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",
|
||||
"done": "Terminé.",
|
||||
"downloading": "Téléchargement...",
|
||||
"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_removed": "Tâche cron pour DynDNS enlevée avec succès",
|
||||
"port_available": "Le port {} est disponible",
|
||||
"port_unavailable": "Le port {} n'est pas disponible",
|
||||
"port_already_opened": "Le port {} est déjà ouvert pour les connexions {:s}",
|
||||
"port_already_closed": "Le port {} est déjà fermé pour les connexions {: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.",
|
||||
"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.",
|
||||
"upnp_dev_not_found": "Aucun périphérique compatible UPnP trouvé",
|
||||
"upnp_port_open_failed": "Impossible d'ouvrir les ports avec UPnP",
|
||||
"upnp_enabled": "UPnP activé avec succè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.",
|
||||
"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_key_generating": "La clé DNS est en cours de génération, cela peut prendre du temps...",
|
||||
"dyndns_registered": "Domaine DynDNS enregistré avec succès",
|
||||
"dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {:s}",
|
||||
"dyndns_unavailable": "Sous-domaine DynDNS indisponible",
|
||||
"executing_script": "Exécution du script...",
|
||||
"extracting": "Extraction...",
|
||||
"field_invalid": "Champ incorrect : {:s}",
|
||||
"firewall_reload_failed": "Impossible de recharger le pare-feu",
|
||||
"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_name_unknown": "Nom de script '{:s}' inconnu",
|
||||
"hook_choice_invalid": "Choix incorrect : '{:s}'",
|
||||
"hook_argument_missing": "Argument manquant : '{:s}'",
|
||||
"mountpoint_unknown": "Point de montage inconnu",
|
||||
"unit_unknown": "Unité '{:s}' inconnue",
|
||||
"monitor_period_invalid": "Période de temps incorrect",
|
||||
"monitor_stats_no_update": "Aucune donnée de l'état du serveur à mettre à jour",
|
||||
"monitor_stats_file_not_found": "Fichier de données de l'état du serveur introuvable",
|
||||
"monitor_stats_period_unavailable": "Aucune donnée de l'état du serveur disponible pour la période",
|
||||
"monitor_enabled": "Suivi de l'état du serveur activé avec succès",
|
||||
"installation_complete": "Installation terminée",
|
||||
"installation_failed": "Échec de l'installation",
|
||||
"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.",
|
||||
"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.",
|
||||
"ldap_initialized": "Répertoire LDAP initialisé avec succès",
|
||||
"license_undefined": "indéfinie",
|
||||
"mail_alias_remove_failed": "Impossible de supprimer l'adresse mail supplémentaire '{:s}'",
|
||||
"mail_domain_unknown": "Domaine '{:s}' de l'adresse mail inconnu",
|
||||
"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_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",
|
||||
"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} n’existe 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_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 l’option --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_removed": "Service enlevé avec succè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_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_no_log": "Aucun journal a afficher pour le service '{:s}'",
|
||||
"service_cmd_exec_failed": "Impossible d'exécuter la commande '{:s}'",
|
||||
"ldap_initialized": "Répertoire LDAP initialisé avec succès",
|
||||
"admin_password_change_failed": "Impossible de modifier le mot de passe d'administration",
|
||||
"admin_password_changed": "Mot de passe d'administration modifié avec succès",
|
||||
"new_domain_required": "Vous devez spécifier le nouveau domaine principal",
|
||||
"maindomain_change_failed": "Impossible de modifier le domaine principal",
|
||||
"maindomain_changed": "Domaine principal modifié avec succès",
|
||||
"yunohost_installing": "Installation de YunoHost...",
|
||||
"service_stop_failed": "Impossible d'arrêter le service '{:s}'",
|
||||
"service_stopped": "Service '{:s}' arrêté avec succès",
|
||||
"service_unknown": "Service '{:s}' inconnu",
|
||||
"services_configured": "La configuration a été générée avec succès",
|
||||
"ssowat_conf_generated": "Configuration de SSOwat générée avec succès",
|
||||
"ssowat_conf_updated": "Configuration persistante de SSOwat mise à jour avec succès",
|
||||
"system_upgraded": "Système mis à jour avec succès",
|
||||
"system_username_exists": "Le nom d'utilisateur existe déjà dans les utilisateurs système",
|
||||
"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_ca_creation_failed": "Impossible de créer l'autorité de certification",
|
||||
"yunohost_configured": "YunoHost configuré avec succès",
|
||||
"updating_apt_cache": "Mise à jour de la liste des paquets disponibles...",
|
||||
"update_cache_failed": "Impossible de mettre à jour le cache de l'APT",
|
||||
"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"
|
||||
"yunohost_installing": "Installation de YunoHost...",
|
||||
"yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'."
|
||||
}
|
||||
|
|
1
locales/it.json
Normal file
1
locales/it.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
66
locales/nl.json
Normal file
66
locales/nl.json
Normal 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"
|
||||
}
|
228
locales/pt.json
228
locales/pt.json
|
@ -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}'",
|
||||
"license_undefined": "indefinido",
|
||||
"no_appslist_found": "Não foi encontrada a lista de aplicações",
|
||||
"custom_appslist_name_required": "Deve fornecer um nome para a sua lista de aplicações personalizada",
|
||||
"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",
|
||||
"admin_password": "Senha de administração",
|
||||
"admin_password_change_failed": "Não foi possível alterar a senha",
|
||||
"admin_password_changed": "Senha de administração alterada com êxito",
|
||||
"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_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_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",
|
||||
"ssowat_conf_updated": "Configuração persistente SSOwat atualizada com êxito",
|
||||
"ssowat_conf_generated": "Configuração SSOwat gerada com êxito",
|
||||
"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",
|
||||
"extracting": "Extração em curso...",
|
||||
"downloading": "Transferência em curso...",
|
||||
"executing_script": "A executar o script...",
|
||||
"done": "Concluído.",
|
||||
"path_removal_failed": "Incapaz remover o caminho {:s}",
|
||||
"domain_unknown": "Domínio desconhecido",
|
||||
"domain_dyndns_invalid": "Domínio inválido para ser utilizado com DynDNS",
|
||||
"domain_dyndns_already_subscribed": "Já subscreveu um domínio DynDNS",
|
||||
"domain_dyndns_root_unknown": "Domínio root (administrador) DynDNS desconhecido",
|
||||
"app_unknown": "Aplicação desconhecida",
|
||||
"app_upgrade_failed": "Unable to upgrade all apps",
|
||||
"app_upgraded": "{:s} atualizada com êxito",
|
||||
"appslist_fetched": "Lista de aplicações processada com êxito",
|
||||
"appslist_removed": "Lista de aplicações removida com êxito",
|
||||
"appslist_retrieve_error": "Não foi possível obter a lista de aplicações remotas",
|
||||
"appslist_unknown": "Lista de aplicaçoes desconhecida",
|
||||
"ask_current_admin_password": "Senha de administração atual",
|
||||
"ask_email": "Correio eletrónico",
|
||||
"ask_firstname": "Primeiro nome",
|
||||
"ask_lastname": "Último nome",
|
||||
"ask_list_to_remove": "Lista para remover",
|
||||
"ask_main_domain": "Domínio principal",
|
||||
"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_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_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_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",
|
||||
"domain_created": "Domínio criado com êxito",
|
||||
"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",
|
||||
"done": "Concluído.",
|
||||
"downloading": "Transferência em curso...",
|
||||
"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_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",
|
||||
"hook_choice_invalid": "Escolha inválida '{:s}'",
|
||||
"hook_argument_missing": "Argumento em falta '{:s}'",
|
||||
"mountpoint_unknown": "Ponto de montagem desconhecido",
|
||||
"unit_unknown": "Unidade desconhecida '{:s}'",
|
||||
"monitor_period_invalid": "Período de tempo inválido",
|
||||
"monitor_stats_no_update": "Não existem estatísticas de monitorização para atualizar",
|
||||
"monitor_stats_file_not_found": "Ficheiro de estatísticas não encontrado",
|
||||
"monitor_stats_period_unavailable": "Não existem estatísticas disponíveis para este período",
|
||||
"monitor_enabled": "Monitorização do servidor ativada com êxito",
|
||||
"hook_choice_invalid": "Escolha inválida '{:s}'",
|
||||
"installation_complete": "Instalação concluída",
|
||||
"installation_failed": "A instalação falhou",
|
||||
"iptables_unavailable": "Não pode alterar aqui a iptables. Ou o seu kernel não o suporta ou está num espaço reservado.",
|
||||
"ldap_initialized": "LDAP inicializada com êxito",
|
||||
"license_undefined": "indefinido",
|
||||
"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_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",
|
||||
"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_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_removed": "Serviço eliminado com êxito",
|
||||
"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_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_no_log": "Não existem registos para mostrar do serviço '{:s}'",
|
||||
"service_cmd_exec_failed": "Incapaz executar o comando '{:s}'",
|
||||
"ldap_initialized": "LDAP inicializada com êxito",
|
||||
"admin_password_change_failed": "Não foi possível alterar a senha",
|
||||
"admin_password_changed": "Senha de administração alterada com êxito",
|
||||
"new_domain_required": "Deve escrever um novo domínio principal",
|
||||
"maindomain_change_failed": "Incapaz alterar o domínio raiz",
|
||||
"maindomain_changed": "Domínio raiz alterado com êxito",
|
||||
"yunohost_installing": "A instalar a YunoHost...",
|
||||
"service_stop_failed": "Incapaz parar o serviço '{:s}",
|
||||
"service_stopped": "O serviço '{:s}' foi parado com êxito",
|
||||
"service_unknown": "Serviço desconhecido '{:s}'",
|
||||
"ssowat_conf_generated": "Configuração SSOwat gerada com êxito",
|
||||
"ssowat_conf_updated": "Configuração persistente SSOwat atualizada com êxito",
|
||||
"system_upgraded": "Sistema atualizado com êxito",
|
||||
"system_username_exists": "O utilizador já existe no registo do sistema",
|
||||
"unexpected_error": "Ocorreu um erro inesperado",
|
||||
"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_ca_creation_failed": "Incapaz criar o certificado de autoridade",
|
||||
"yunohost_configured": "YunoHost configurada com êxito",
|
||||
"updating_apt_cache": "A atualizar a lista de pacotes disponíveis...",
|
||||
"update_cache_failed": "Não foi possível atualizar os cabeçalhos APT",
|
||||
"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)"
|
||||
"yunohost_installing": "A instalar a YunoHost...",
|
||||
"yunohost_not_installed": "YunoHost ainda não está corretamente configurado. Por favor execute as 'ferramentas pós-instalação yunohost'"
|
||||
}
|
||||
|
|
1
locales/tr.json
Normal file
1
locales/tr.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
|
@ -24,15 +24,26 @@
|
|||
## Packages versions
|
||||
|
||||
def get_version(package):
|
||||
"""Get the version of package"""
|
||||
from moulinette.utils import process
|
||||
return process.check_output(
|
||||
"dpkg-query -W -f='${{Version}}' {0}".format(package)
|
||||
).strip()
|
||||
|
||||
def get_versions(*args, **kwargs):
|
||||
"""Get the version of each YunoHost package"""
|
||||
from collections import OrderedDict
|
||||
return OrderedDict([
|
||||
('moulinette', get_version('moulinette')),
|
||||
('moulinette-yunohost', get_version('moulinette-yunohost')),
|
||||
('yunohost', get_version('yunohost')),
|
||||
('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
|
|
@ -39,6 +39,9 @@ import subprocess
|
|||
from moulinette.core import MoulinetteError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
from . import has_min_version
|
||||
from .service import service_log
|
||||
|
||||
logger = getActionLogger('yunohost.app')
|
||||
|
||||
repo_path = '/var/cache/yunohost/repo'
|
||||
|
@ -70,7 +73,7 @@ def app_fetchlist(url=None, name=None):
|
|||
|
||||
Keyword argument:
|
||||
name -- Name of the list (default yunohost)
|
||||
url -- URL of remote JSON list (default https://yunohost.org/list.json)
|
||||
url -- URL of remote JSON list (default https://yunohost.org/official.json)
|
||||
|
||||
"""
|
||||
# Create app path if not exists
|
||||
|
@ -78,7 +81,7 @@ def app_fetchlist(url=None, name=None):
|
|||
except OSError: os.makedirs(repo_path)
|
||||
|
||||
if url is None:
|
||||
url = 'https://yunohost.org/list.json'
|
||||
url = 'https://yunohost.org/official.json'
|
||||
name = 'yunohost'
|
||||
else:
|
||||
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]
|
||||
|
||||
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 (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)
|
||||
|
||||
if raw:
|
||||
app_info['installed'] = installed
|
||||
app_info_dict['installed'] = installed
|
||||
if installed:
|
||||
app_info['status'] = _get_app_status(app_id)
|
||||
list_dict[app_id] = app_info
|
||||
app_info_dict['status'] = _get_app_status(app_id)
|
||||
list_dict[app_id] = app_info_dict
|
||||
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({
|
||||
'id': app_id,
|
||||
'name': app_info['manifest']['name'],
|
||||
'name': app_info_dict['manifest']['name'],
|
||||
'label': label,
|
||||
'description': _value_for_locale(
|
||||
app_info['manifest']['description']),
|
||||
app_info_dict['manifest']['description']),
|
||||
# FIXME: Temporarly allow undefined license
|
||||
'license': app_info['manifest'].get('license',
|
||||
'license': app_info_dict['manifest'].get('license',
|
||||
m18n.n('license_undefined')),
|
||||
'installed': installed
|
||||
})
|
||||
|
@ -207,11 +215,10 @@ def app_info(app, show_status=False, raw=False):
|
|||
"""
|
||||
if not _is_installed(app):
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('app_not_installed', app))
|
||||
m18n.n('app_not_installed', app=app))
|
||||
if raw:
|
||||
ret = app_list(filter=app, raw=True)[app]
|
||||
with open(apps_setting_path + app +'/settings.yml') as f:
|
||||
ret['settings'] = yaml.load(f)
|
||||
ret['settings'] = _get_app_settings(app)
|
||||
return ret
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
|
||||
apps = []
|
||||
result = {}
|
||||
|
||||
for app_id in os.listdir(apps_setting_path):
|
||||
if app and (app != app_id):
|
||||
if app is not None:
|
||||
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
|
||||
|
||||
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:
|
||||
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 app_settings['domain'] not in result:
|
||||
result[app_settings['domain']] = {}
|
||||
result[app_settings['domain']][app_settings['path']] = {
|
||||
'label': app_settings['label'],
|
||||
'id': app_settings['id']
|
||||
if domain not in result:
|
||||
result[domain] = {}
|
||||
result[domain][path] = {
|
||||
'label': app_settings['label'],
|
||||
'id': app_settings['id']
|
||||
}
|
||||
else:
|
||||
result[app_settings['domain']+app_settings['path']] = app_settings['label']
|
||||
result[domain + path] = app_settings['label']
|
||||
|
||||
return result
|
||||
|
||||
|
@ -307,18 +323,13 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
|||
installed = _is_installed(app_id)
|
||||
if not installed:
|
||||
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:
|
||||
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)
|
||||
new_app_dict = app_info(original_app_id, raw=True)
|
||||
new_app_dict = app_info(app_id, raw=True)
|
||||
|
||||
if file:
|
||||
manifest = _extract_app_from_file(file)
|
||||
|
@ -337,9 +348,11 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
|||
continue
|
||||
|
||||
# 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,
|
||||
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
|
||||
|
||||
|
@ -347,36 +360,20 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
|||
status = _get_app_status(app_id)
|
||||
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
|
||||
hook_remove(app_id)
|
||||
if 'hooks' in os.listdir(app_tmp_folder):
|
||||
for hook in os.listdir(app_tmp_folder +'/hooks'):
|
||||
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
|
||||
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'))
|
||||
else:
|
||||
now = int(time.time())
|
||||
|
@ -394,7 +391,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
|||
|
||||
# So much win
|
||||
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:
|
||||
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']
|
||||
|
||||
# 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,
|
||||
m18n.n('app_recent_version_required', app_id))
|
||||
m18n.n('app_recent_version_required',
|
||||
app=app_id))
|
||||
|
||||
# Check if app can be forked
|
||||
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,
|
||||
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
|
||||
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''+ 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
|
||||
# Retrieve arguments list for install script
|
||||
args_dict = {} if not args else \
|
||||
dict(urlparse.parse_qsl(args, keep_blank_values=True))
|
||||
args_list = _parse_args_from_manifest(manifest, 'install', args_dict, auth)
|
||||
args_list.append(app_id)
|
||||
|
||||
# Prepare App settings
|
||||
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)
|
||||
|
||||
try:
|
||||
if args is None:
|
||||
args = ''
|
||||
args_dict = dict(urlparse.parse_qsl(args, keep_blank_values=True))
|
||||
except:
|
||||
args_dict = {}
|
||||
|
||||
# Execute App install script
|
||||
os.system('chown -hR admin: %s' % install_tmp)
|
||||
# Move scripts and manifest to the right place
|
||||
os.system('cp %s/manifest.json %s' % (app_tmp_folder, app_setting_path))
|
||||
os.system('cp -R %s/scripts %s' % (app_tmp_folder, app_setting_path))
|
||||
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
|
||||
with open(app_setting_path + '/status.json', 'w+') as f:
|
||||
json.dump(status, f)
|
||||
|
@ -561,7 +537,8 @@ def app_remove(auth, app):
|
|||
from yunohost.hook import hook_exec, hook_remove
|
||||
|
||||
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
|
||||
|
||||
|
@ -574,8 +551,10 @@ def app_remove(auth, app):
|
|||
os.system('chown -R admin: /tmp/yunohost_remove')
|
||||
os.system('chmod -R u+rX /tmp/yunohost_remove')
|
||||
|
||||
if hook_exec('/tmp/yunohost_remove/scripts/remove') == 0:
|
||||
msignals.display(m18n.n('app_removed', app), 'success')
|
||||
args_list = [app]
|
||||
|
||||
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)
|
||||
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.hook import hook_callback
|
||||
|
||||
result = {}
|
||||
|
||||
if not users:
|
||||
users = user_list(auth)['users'].keys()
|
||||
|
||||
if not isinstance(users, list): users = [users]
|
||||
if not isinstance(apps, list): apps = [apps]
|
||||
elif not isinstance(users, list):
|
||||
users = [users,]
|
||||
if not isinstance(apps, list):
|
||||
apps = [apps,]
|
||||
|
||||
for app in apps:
|
||||
if not _is_installed(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_settings = _get_app_settings(app)
|
||||
if not app_settings:
|
||||
continue
|
||||
|
||||
if 'mode' not in app_settings:
|
||||
app_setting(app, 'mode', 'private')
|
||||
app_settings['mode'] = 'private'
|
||||
|
||||
if app_settings['mode'] == 'private':
|
||||
allowed_users = set()
|
||||
if 'allowed_users' in app_settings:
|
||||
new_users = app_settings['allowed_users']
|
||||
else:
|
||||
new_users = ''
|
||||
allowed_users = set(app_settings['allowed_users'].split(','))
|
||||
|
||||
for allowed_user in users:
|
||||
if allowed_user not in new_users.split(','):
|
||||
if allowed_user not in allowed_users:
|
||||
try:
|
||||
user_info(auth, allowed_user)
|
||||
except MoulinetteError:
|
||||
# FIXME: Add username keyword in user_unknown
|
||||
logger.warning('{0}{1}'.format(
|
||||
m18n.g('colon', m18n.n('user_unknown')),
|
||||
allowed_user))
|
||||
continue
|
||||
if new_users == '':
|
||||
new_users = allowed_user
|
||||
else:
|
||||
new_users = new_users +','+ allowed_user
|
||||
allowed_users.add(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])
|
||||
|
||||
result[app] = allowed_users
|
||||
|
||||
app_ssowatconf(auth)
|
||||
|
||||
return { 'allowed_users': new_users.split(',') }
|
||||
return { 'allowed_users': result }
|
||||
|
||||
|
||||
def app_removeaccess(auth, apps, users=[]):
|
||||
|
@ -650,45 +632,43 @@ def app_removeaccess(auth, apps, users=[]):
|
|||
from yunohost.user import user_list
|
||||
from yunohost.hook import hook_callback
|
||||
|
||||
result = {}
|
||||
|
||||
remove_all = False
|
||||
if not users:
|
||||
remove_all = True
|
||||
if not isinstance(users, list): users = [users]
|
||||
if not isinstance(apps, list): apps = [apps]
|
||||
elif not isinstance(users, list):
|
||||
users = [users,]
|
||||
if not isinstance(apps, list):
|
||||
apps = [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):
|
||||
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 app_settings.get('skipped_uris', '') != '/':
|
||||
if remove_all:
|
||||
new_users = ''
|
||||
pass
|
||||
elif 'allowed_users' in app_settings:
|
||||
for allowed_user in app_settings['allowed_users'].split(','):
|
||||
if allowed_user not in users:
|
||||
if new_users == '':
|
||||
new_users = allowed_user
|
||||
else:
|
||||
new_users = new_users +','+ allowed_user
|
||||
allowed_users.add(allowed_user)
|
||||
else:
|
||||
new_users = ''
|
||||
for username in user_list(auth)['users'].keys():
|
||||
if username not in users:
|
||||
if new_users == '':
|
||||
new_users = username
|
||||
new_users += ',' + username
|
||||
for allowed_user in user_list(auth)['users'].keys():
|
||||
if allowed_user not in users:
|
||||
allowed_users.add(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_removeaccess', args=[app, new_users])
|
||||
|
||||
result[app] = allowed_users
|
||||
|
||||
app_ssowatconf(auth)
|
||||
|
||||
return { 'allowed_users': new_users.split(',') }
|
||||
return { 'allowed_users': result }
|
||||
|
||||
|
||||
def app_clearaccess(auth, apps):
|
||||
|
@ -704,12 +684,9 @@ def app_clearaccess(auth, apps):
|
|||
if not isinstance(apps, list): apps = [apps]
|
||||
|
||||
for app in apps:
|
||||
if not _is_installed(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_settings = _get_app_settings(app)
|
||||
if not app_settings:
|
||||
continue
|
||||
|
||||
if 'mode' in app_settings:
|
||||
app_setting(app, 'mode', delete=True)
|
||||
|
@ -722,6 +699,29 @@ def app_clearaccess(auth, apps):
|
|||
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):
|
||||
"""
|
||||
Redirect domain root to an app
|
||||
|
@ -733,12 +733,7 @@ def app_makedefault(auth, app, domain=None):
|
|||
"""
|
||||
from yunohost.domain import domain_list
|
||||
|
||||
if not _is_installed(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_settings = _get_app_settings(app)
|
||||
app_domain = app_settings['domain']
|
||||
app_path = app_settings['path']
|
||||
|
||||
|
@ -781,23 +776,13 @@ def app_setting(app, key, value=None, delete=False):
|
|||
delete -- Delete the key
|
||||
|
||||
"""
|
||||
if not _is_installed(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 = {}
|
||||
app_settings = _get_app_settings(app)
|
||||
|
||||
if value is None and not delete:
|
||||
try:
|
||||
return app_settings[key]
|
||||
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
|
||||
else:
|
||||
yaml_settings=['redirected_urls','redirected_regex']
|
||||
|
@ -811,7 +796,8 @@ def app_setting(app, key, value=None, delete=False):
|
|||
value=yaml.load(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)
|
||||
|
||||
|
||||
|
@ -1011,6 +997,29 @@ def app_ssowatconf(auth):
|
|||
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):
|
||||
"""
|
||||
Get app status or create it if needed
|
||||
|
@ -1304,6 +1313,116 @@ def _encode_string(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):
|
||||
"""
|
||||
Convert a string into a boolean
|
|
@ -32,6 +32,7 @@ import time
|
|||
import tarfile
|
||||
import shutil
|
||||
import subprocess
|
||||
from glob import glob
|
||||
from collections import OrderedDict
|
||||
|
||||
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
|
||||
from yunohost.hook import hook_callback, hook_exec
|
||||
from yunohost.hook import hook_info, hook_callback, hook_exec
|
||||
|
||||
tmp_dir = None
|
||||
|
||||
# Validate what to backup
|
||||
if ignore_hooks and ignore_apps:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('backup_action_required'))
|
||||
m18n.n('backup_action_required'))
|
||||
|
||||
# Validate and define backup name
|
||||
timestamp = int(time.time())
|
||||
if not name:
|
||||
name = str(timestamp)
|
||||
name = time.strftime('%Y%m%d-%H%M%S')
|
||||
if name in backup_list()['archives']:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('backup_archive_name_exists'))
|
||||
m18n.n('backup_archive_name_exists'))
|
||||
|
||||
# Validate additional arguments
|
||||
if no_compress and not output_directory:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('backup_output_directory_required'))
|
||||
m18n.n('backup_output_directory_required'))
|
||||
if 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 \
|
||||
re.match(r'^/(|(bin|boot|dev|etc|lib|root|run|sbin|sys|usr|var)(|/.*))$',
|
||||
output_directory):
|
||||
logger.error("forbidden output directory '%'", output_directory)
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('backup_output_directory_forbidden'))
|
||||
m18n.n('backup_output_directory_forbidden'))
|
||||
|
||||
# Create the 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)
|
||||
# Check that output directory is empty
|
||||
elif no_compress and os.listdir(output_directory):
|
||||
logger.error("not empty output directory '%'", output_directory)
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_output_directory_not_empty'))
|
||||
m18n.n('backup_output_directory_not_empty'))
|
||||
|
||||
# Define temporary directory
|
||||
if no_compress:
|
||||
|
@ -114,8 +113,8 @@ def backup_create(name=None, description=None, output_directory=None,
|
|||
if not tmp_dir:
|
||||
tmp_dir = "%s/tmp/%s" % (backup_path, name)
|
||||
if os.path.isdir(tmp_dir):
|
||||
logger.warning("temporary directory for backup '%s' already exists",
|
||||
tmp_dir)
|
||||
logger.debug("temporary directory for backup '%s' already exists",
|
||||
tmp_dir)
|
||||
filesystem.rm(tmp_dir, recursive=True)
|
||||
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']:
|
||||
filesystem.rm(tmp_dir, True, True)
|
||||
else:
|
||||
msignals.display(m18n.n('backup_cleaning_failed'), 'warning')
|
||||
logger.warning(m18n.n('backup_cleaning_failed'))
|
||||
|
||||
# Initialize backup info
|
||||
info = {
|
||||
|
@ -136,9 +135,35 @@ def backup_create(name=None, description=None, output_directory=None,
|
|||
|
||||
# Run system hooks
|
||||
if not ignore_hooks:
|
||||
msignals.display(m18n.n('backup_running_hooks'))
|
||||
hooks_ret = hook_callback('backup', hooks, args=[tmp_dir])
|
||||
info['hooks'] = hooks_ret['succeed']
|
||||
# Check hooks availibility
|
||||
hooks_filtered = set()
|
||||
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
|
||||
if not ignore_apps:
|
||||
|
@ -150,8 +175,7 @@ def backup_create(name=None, description=None, output_directory=None,
|
|||
if apps:
|
||||
for a in apps:
|
||||
if a not in apps_list:
|
||||
logger.warning("app '%s' not found", a)
|
||||
msignals.display(m18n.n('unbackup_app', a), 'warning')
|
||||
logger.warning(m18n.n('unbackup_app', app=a))
|
||||
else:
|
||||
apps_filtered.add(a)
|
||||
else:
|
||||
|
@ -162,33 +186,18 @@ def backup_create(name=None, description=None, output_directory=None,
|
|||
for app_id in apps_filtered:
|
||||
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 script
|
||||
# Check if the app has a backup and restore script
|
||||
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'
|
||||
if os.path.isfile(app_script):
|
||||
try:
|
||||
filesystem.mkdir(tmp_app_dir, 0750, True, uid='admin')
|
||||
shutil.copy(app_restore_script, tmp_app_dir)
|
||||
except:
|
||||
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')
|
||||
if not os.path.isfile(app_script):
|
||||
logger.warning(m18n.n('unbackup_app', app=app_id))
|
||||
continue
|
||||
elif not os.path.isfile(app_restore_script):
|
||||
logger.warning(m18n.n('unrestore_app', app=app_id))
|
||||
|
||||
tmp_app_dir = '{:s}/apps/{:s}'.format(tmp_dir, app_id)
|
||||
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:
|
||||
# Prepare backup directory for the app
|
||||
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
|
||||
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:
|
||||
logger.exception("error while executing backup of '%s'", app_id)
|
||||
msignals.display(m18n.n('backup_app_failed', app=app_id),
|
||||
'error')
|
||||
logger.exception(m18n.n('backup_app_failed', app=app_id))
|
||||
# Cleaning app backup directory
|
||||
shutil.rmtree(tmp_app_dir, ignore_errors=True)
|
||||
else:
|
||||
|
@ -214,10 +222,10 @@ def backup_create(name=None, description=None, output_directory=None,
|
|||
finally:
|
||||
filesystem.rm(tmp_script, force=True)
|
||||
|
||||
# Check if something has been saved
|
||||
if ignore_hooks and not info['apps']:
|
||||
_clean_tmp_dir(1)
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('backup_nothings_done'))
|
||||
# Check if something has been saved
|
||||
if not info['hooks'] and not info['apps']:
|
||||
_clean_tmp_dir(1)
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('backup_nothings_done'))
|
||||
|
||||
# Create backup info file
|
||||
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
|
||||
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)
|
||||
try:
|
||||
tar = tarfile.open(archive_file, "w:gz")
|
||||
|
@ -238,17 +246,16 @@ def backup_create(name=None, description=None, output_directory=None,
|
|||
try:
|
||||
tar = tarfile.open(archive_file, "w:gz")
|
||||
except:
|
||||
logger.exception("unable to open '%s' for writing "
|
||||
"after creating directory '%s'",
|
||||
archive_file, archives_path)
|
||||
logger.debug("unable to open '%s' for writing",
|
||||
archive_file, exc_info=1)
|
||||
tar = None
|
||||
else:
|
||||
logger.exception("unable to open the archive '%s' for writing",
|
||||
archive_file)
|
||||
logger.debug("unable to open '%s' for writing",
|
||||
archive_file, exc_info=1)
|
||||
if tar is None:
|
||||
_clean_tmp_dir(2)
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_archive_open_failed'))
|
||||
m18n.n('backup_archive_open_failed'))
|
||||
tar.add(tmp_dir, arcname='')
|
||||
tar.close()
|
||||
|
||||
|
@ -260,7 +267,7 @@ def backup_create(name=None, description=None, output_directory=None,
|
|||
if tmp_dir != output_directory:
|
||||
_clean_tmp_dir()
|
||||
|
||||
msignals.display(m18n.n('backup_complete'), 'success')
|
||||
logger.success(m18n.n('backup_complete'))
|
||||
|
||||
# Return backup info
|
||||
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
|
||||
|
||||
"""
|
||||
from yunohost.hook import hook_add
|
||||
from yunohost.hook import hook_callback
|
||||
from yunohost.hook import hook_exec
|
||||
from yunohost.hook import hook_info, hook_callback, hook_exec
|
||||
from yunohost.hook import custom_hook_folder
|
||||
|
||||
# Validate what to restore
|
||||
if ignore_hooks and ignore_apps:
|
||||
raise MoulinetteError(errno.EINVAL,
|
||||
m18n.n('restore_action_required'))
|
||||
|
||||
# Retrieve and open the archive
|
||||
info = backup_info(name)
|
||||
|
@ -289,37 +300,50 @@ def backup_restore(name, hooks=[], apps=[], ignore_apps=False, ignore_hooks=Fals
|
|||
try:
|
||||
tar = tarfile.open(archive_file, "r:gz")
|
||||
except:
|
||||
logger.exception("unable to open the archive '%s' for reading",
|
||||
archive_file)
|
||||
logger.debug("cannot open backup archive '%s'",
|
||||
archive_file, exc_info=1)
|
||||
raise MoulinetteError(errno.EIO, m18n.n('backup_archive_open_failed'))
|
||||
|
||||
# Check temporary directory
|
||||
tmp_dir = "%s/tmp/%s" % (backup_path, name)
|
||||
if os.path.isdir(tmp_dir):
|
||||
logger.warning("temporary directory for restoration '%s' already exists",
|
||||
tmp_dir)
|
||||
logger.debug("temporary directory for restoration '%s' already exists",
|
||||
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
|
||||
msignals.display(m18n.n('backup_extracting_archive'))
|
||||
logger.info(m18n.n('backup_extracting_archive'))
|
||||
tar.extractall(tmp_dir)
|
||||
tar.close()
|
||||
|
||||
# Retrieve backup info
|
||||
info_file = "%s/info.json" % tmp_dir
|
||||
try:
|
||||
with open("%s/info.json" % tmp_dir, 'r') as f:
|
||||
with open(info_file, 'r') as f:
|
||||
info = json.load(f)
|
||||
except IOError:
|
||||
logger.error("unable to retrieve backup info from '%s/info.json'",
|
||||
tmp_dir)
|
||||
logger.debug("unable to load '%s'", info_file, exc_info=1)
|
||||
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
|
||||
else:
|
||||
logger.info("restoring from backup '%s' created on %s", name,
|
||||
time.ctime(info['created_at']))
|
||||
logger.debug("restoring from backup '%s' created on %s", name,
|
||||
time.ctime(info['created_at']))
|
||||
|
||||
# Initialize restauration summary result
|
||||
result = {
|
||||
'apps': [],
|
||||
'hooks': {},
|
||||
}
|
||||
|
||||
# Check if YunoHost is 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:
|
||||
try:
|
||||
# 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:
|
||||
domain = f.readline().rstrip()
|
||||
except IOError:
|
||||
logger.error("unable to retrieve domain from '%s/yunohost/current_host'",
|
||||
tmp_dir)
|
||||
logger.debug("unable to retrieve domain from "
|
||||
"'%s/yunohost/current_host'", tmp_dir, exc_info=1)
|
||||
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)
|
||||
|
||||
# Run hooks
|
||||
# Run system hooks
|
||||
if not ignore_hooks:
|
||||
if hooks is None or len(hooks)==0:
|
||||
hooks=info['hooks'].keys()
|
||||
# Filter hooks to execute
|
||||
hooks_list = set(info['hooks'].keys())
|
||||
_is_hook_in_backup = lambda h: True
|
||||
if hooks:
|
||||
def _is_hook_in_backup(h):
|
||||
if h in hooks_list:
|
||||
return True
|
||||
logger.error(m18n.n('backup_archive_hook_not_exec', hook=h))
|
||||
return False
|
||||
else:
|
||||
hooks = hooks_list
|
||||
|
||||
hooks_filtered=list(set(hooks) & set(info['hooks'].keys()))
|
||||
hooks_unexecuted=set(hooks) - set(info['hooks'].keys())
|
||||
for hook in hooks_unexecuted:
|
||||
logger.warning("hook '%s' not in this backup", hook)
|
||||
msignals.display(m18n.n('backup_hook_unavailable', hook), 'warning')
|
||||
msignals.display(m18n.n('restore_running_hooks'))
|
||||
hook_callback('restore', hooks_filtered, args=[tmp_dir])
|
||||
# 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
|
||||
if not ignore_apps:
|
||||
from yunohost.app import _is_installed
|
||||
|
||||
# Filter applications to restore
|
||||
apps_list = set(info['apps'].keys())
|
||||
apps_filtered = set()
|
||||
if not apps:
|
||||
apps=apps_list
|
||||
|
||||
from yunohost.app import _is_installed
|
||||
for app_id in apps:
|
||||
if app_id not in apps_list:
|
||||
logger.warning("app '%s' not found", app_id)
|
||||
msignals.display(m18n.n('unrestore_app', app_id), 'warning')
|
||||
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)
|
||||
if apps:
|
||||
for a in apps:
|
||||
if a not in apps_list:
|
||||
logger.error(m18n.n('backup_archive_app_not_found', app=a))
|
||||
else:
|
||||
apps_filtered.add(a)
|
||||
else:
|
||||
apps_filtered = apps_list
|
||||
|
||||
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:
|
||||
# Copy app settings
|
||||
app_setting_path = '/etc/yunohost/apps/' + app_id
|
||||
shutil.copytree(app_bkp_dir + '/settings', app_setting_path )
|
||||
# Copy app settings and set permissions
|
||||
shutil.copytree(tmp_app_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
|
||||
app_restore_script=app_bkp_dir+'/restore'
|
||||
tmp_script = '/tmp/restore_%s_%s' % (name,app_id)
|
||||
subprocess.call(['install', '-Dm555', app_restore_script, tmp_script])
|
||||
hook_exec(tmp_script, args=[app_bkp_dir+'/backup', app_id])
|
||||
|
||||
subprocess.call(['install', '-Dm555', app_script, tmp_script])
|
||||
hook_exec(tmp_script, args=[tmp_app_dir + '/backup', app_id],
|
||||
raise_on_error=True)
|
||||
except:
|
||||
logger.exception("error while restoring backup of '%s'", app_id)
|
||||
msignals.display(m18n.n('restore_app_failed', app=app_id),
|
||||
'error')
|
||||
# Cleaning settings directory
|
||||
shutil.rmtree(app_setting_path + '/settings', ignore_errors=True)
|
||||
logger.exception(m18n.n('restore_app_failed', app=app_id))
|
||||
# Cleaning app directory
|
||||
shutil.rmtree(app_setting_path, ignore_errors=True)
|
||||
else:
|
||||
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
|
||||
os.system('rm -rf %s' % tmp_dir)
|
||||
_clean_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):
|
||||
|
@ -423,8 +490,8 @@ def backup_list(with_info=False, human_readable=False):
|
|||
try:
|
||||
# Retrieve local archives
|
||||
archives = os.listdir(archives_path)
|
||||
except OSError as e:
|
||||
logger.info("unable to iterate over local archives: %s", str(e))
|
||||
except OSError:
|
||||
logger.debug("unable to iterate over local archives", exc_info=1)
|
||||
else:
|
||||
# Iterate over local 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)
|
||||
if not os.path.isfile(archive_file):
|
||||
logger.error("no local backup archive found at '%s'", archive_file)
|
||||
raise MoulinetteError(errno.EIO, m18n.n('backup_archive_name_unknown',name))
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_archive_name_unknown', name=name))
|
||||
|
||||
info_file = "%s/%s.info.json" % (archives_path, name)
|
||||
try:
|
||||
|
@ -468,8 +535,7 @@ def backup_info(name, with_details=False, human_readable=False):
|
|||
info = json.load(f)
|
||||
except:
|
||||
# TODO: Attempt to extract backup info file from tarball
|
||||
logger.exception("unable to retrive backup info file '%s'",
|
||||
info_file)
|
||||
logger.debug("unable to load '%s'", info_file, exc_info=1)
|
||||
raise MoulinetteError(errno.EIO, m18n.n('backup_invalid_archive'))
|
||||
|
||||
size = os.path.getsize(archive_file)
|
||||
|
@ -506,15 +572,15 @@ def backup_delete(name):
|
|||
info_file = "%s/%s.info.json" % (archives_path, name)
|
||||
for backup_file in [archive_file,info_file]:
|
||||
if not os.path.isfile(backup_file):
|
||||
logger.error("no local backup archive found at '%s'", backup_file)
|
||||
raise MoulinetteError(errno.EIO, m18n.n('backup_archive_name_unknown', backup_file))
|
||||
raise MoulinetteError(errno.EIO,
|
||||
m18n.n('backup_archive_name_unknown', name=backup_file))
|
||||
try:
|
||||
os.remove(backup_file)
|
||||
except:
|
||||
logger.exception("unable to delete '%s'", backup_file)
|
||||
logger.debug("unable to delete '%s'", backup_file, exc_info=1)
|
||||
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])
|
||||
|
||||
msignals.display(m18n.n('backup_deleted'), 'success')
|
||||
logger.success(m18n.n('backup_deleted'))
|
|
@ -80,6 +80,7 @@ def domain_add(auth, domain, dyndns=False):
|
|||
|
||||
"""
|
||||
from yunohost.service import service_regenconf
|
||||
from yunohost.hook import hook_callback
|
||||
|
||||
attr_dict = { 'objectClass' : ['mailDomain', 'top'] }
|
||||
try:
|
||||
|
@ -172,6 +173,8 @@ def domain_add(auth, domain, dyndns=False):
|
|||
except: pass
|
||||
raise
|
||||
|
||||
hook_callback('post_domain_add', args=[domain])
|
||||
|
||||
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.hook import hook_callback
|
||||
|
||||
if not force and domain not in domain_list(auth)['domains']:
|
||||
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
|
||||
|
@ -211,4 +215,41 @@ def domain_remove(auth, domain, force=False):
|
|||
service_regenconf(service='dnsmasq')
|
||||
os.system('yunohost app ssowatconf > /dev/null 2>&1')
|
||||
|
||||
hook_callback('post_domain_remove', args=[domain])
|
||||
|
||||
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
|
|
@ -24,7 +24,6 @@
|
|||
Subscribe and Update DynDNS Hosts
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import requests
|
||||
import re
|
||||
import json
|
||||
|
@ -97,7 +96,7 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None
|
|||
# Send subscription
|
||||
try:
|
||||
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'))
|
||||
if r.status_code != 201:
|
||||
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:
|
||||
try:
|
||||
new_ip = requests.get('http://ip.yunohost.org').text
|
||||
except ConnectionError:
|
||||
except requests.ConnectionError:
|
||||
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
|
||||
else:
|
||||
new_ip = ip
|
|
@ -83,8 +83,7 @@ def firewall_allow(protocol, port, ipv4_only=False, ipv6_only=False,
|
|||
firewall[i][p].append(port)
|
||||
else:
|
||||
ipv = "IPv%s" % i[3]
|
||||
msignals.display(m18n.n('port_already_opened', port, ipv),
|
||||
'warning')
|
||||
logger.warning(m18n.n('port_already_opened', port, ipv))
|
||||
# Add port forwarding with UPnP
|
||||
if not no_upnp and port not in firewall['uPnP'][p]:
|
||||
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)
|
||||
|
||||
# Validate port
|
||||
if ':' not in port:
|
||||
if not isinstance(port, int) and ':' not in port:
|
||||
port = int(port)
|
||||
|
||||
# Validate protocols
|
||||
|
@ -141,8 +140,7 @@ def firewall_disallow(protocol, port, ipv4_only=False, ipv6_only=False,
|
|||
firewall[i][p].remove(port)
|
||||
else:
|
||||
ipv = "IPv%s" % i[3]
|
||||
msignals.display(m18n.n('port_already_closed', port, ipv),
|
||||
'warning')
|
||||
logger.warning(m18n.n('port_already_closed', port, ipv))
|
||||
# Remove port forwarding with UPnP
|
||||
if upnp and port in firewall['uPnP'][p]:
|
||||
firewall['uPnP'][p].remove(port)
|
||||
|
@ -188,10 +186,12 @@ def firewall_list(raw=False, by_ip_version=False, list_forwarded=False):
|
|||
return ret
|
||||
|
||||
|
||||
def firewall_reload():
|
||||
def firewall_reload(skip_upnp=False):
|
||||
"""
|
||||
Reload all firewall rules
|
||||
|
||||
Keyword arguments:
|
||||
skip_upnp -- Do not refresh port forwarding using UPnP
|
||||
|
||||
"""
|
||||
from yunohost.hook import hook_callback
|
||||
|
@ -206,15 +206,15 @@ def firewall_reload():
|
|||
|
||||
# Retrieve firewall rules and UPnP status
|
||||
firewall = firewall_list(raw=True)
|
||||
upnp = firewall_upnp()['enabled']
|
||||
upnp = firewall_upnp()['enabled'] if not skip_upnp else False
|
||||
|
||||
# IPv4
|
||||
try:
|
||||
process.check_output("iptables -L")
|
||||
except process.CalledProcessError as e:
|
||||
logger.info('iptables seems to be not available, it outputs:\n%s',
|
||||
prependlines(e.output.rstrip(), '> '))
|
||||
msignals.display(m18n.n('iptables_unavailable'), 'info')
|
||||
logger.debug('iptables seems to be not available, it outputs:\n%s',
|
||||
prependlines(e.output.rstrip(), '> '))
|
||||
logger.warning(m18n.n('iptables_unavailable'))
|
||||
else:
|
||||
rules = [
|
||||
"iptables -F",
|
||||
|
@ -241,9 +241,9 @@ def firewall_reload():
|
|||
try:
|
||||
process.check_output("ip6tables -L")
|
||||
except process.CalledProcessError as e:
|
||||
logger.info('ip6tables seems to be not available, it outputs:\n%s',
|
||||
prependlines(e.output.rstrip(), '> '))
|
||||
msignals.display(m18n.n('ip6tables_unavailable'), 'info')
|
||||
logger.debug('ip6tables seems to be not available, it outputs:\n%s',
|
||||
prependlines(e.output.rstrip(), '> '))
|
||||
logger.warning(m18n.n('ip6tables_unavailable'))
|
||||
else:
|
||||
rules = [
|
||||
"ip6tables -F",
|
||||
|
@ -280,9 +280,9 @@ def firewall_reload():
|
|||
os.system("service fail2ban restart")
|
||||
|
||||
if errors:
|
||||
msignals.display(m18n.n('firewall_rules_cmd_failed'), 'warning')
|
||||
logger.warning(m18n.n('firewall_rules_cmd_failed'))
|
||||
else:
|
||||
msignals.display(m18n.n('firewall_reloaded'), 'success')
|
||||
logger.success(m18n.n('firewall_reloaded'))
|
||||
return firewall_list()
|
||||
|
||||
|
||||
|
@ -304,7 +304,7 @@ def firewall_upnp(action='status', no_refresh=False):
|
|||
|
||||
# Compatibility with previous version
|
||||
if action == 'reload':
|
||||
logger.warning("'reload' action is deprecated and will be removed")
|
||||
logger.info("'reload' action is deprecated and will be removed")
|
||||
try:
|
||||
# Remove old cron job
|
||||
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:
|
||||
f.write('*/50 * * * * root '
|
||||
'/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
|
||||
elif action == 'disable' or (not enabled and action == 'status'):
|
||||
try:
|
||||
|
@ -342,14 +347,14 @@ def firewall_upnp(action='status', no_refresh=False):
|
|||
nb_dev = upnpc.discover()
|
||||
logger.debug('found %d UPnP device(s)', int(nb_dev))
|
||||
if nb_dev < 1:
|
||||
msignals.display(m18n.n('upnp_dev_not_found'), 'error')
|
||||
logger.error(m18n.n('upnp_dev_not_found'))
|
||||
enabled = False
|
||||
else:
|
||||
try:
|
||||
# Select UPnP device
|
||||
upnpc.selectigd()
|
||||
except:
|
||||
logger.exception('unable to select UPnP device')
|
||||
logger.info('unable to select UPnP device', exc_info=1)
|
||||
enabled = False
|
||||
else:
|
||||
# Iterate over ports
|
||||
|
@ -367,11 +372,12 @@ def firewall_upnp(action='status', no_refresh=False):
|
|||
upnpc.addportmapping(port, protocol, upnpc.lanaddr,
|
||||
port, 'yunohost firewall: port %d' % port, '')
|
||||
except:
|
||||
logger.exception('unable to add port %d using UPnP',
|
||||
port)
|
||||
logger.info('unable to add port %d using UPnP',
|
||||
port, exc_info=1)
|
||||
enabled = False
|
||||
|
||||
if enabled != firewall['uPnP']['enabled']:
|
||||
firewall = firewall_list(raw=True)
|
||||
firewall['uPnP']['enabled'] = enabled
|
||||
|
||||
# Make a backup and update firewall file
|
||||
|
@ -382,13 +388,19 @@ def firewall_upnp(action='status', no_refresh=False):
|
|||
if not no_refresh:
|
||||
# Display success message if needed
|
||||
if action == 'enable' and enabled:
|
||||
msignals.display(m18n.n('upnp_enabled'), 'success')
|
||||
logger.success(m18n.n('upnp_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
|
||||
elif action != 'disable' and not enabled:
|
||||
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:
|
||||
raise MoulinetteError(errno.ENXIO, m18n.n('upnp_port_open_failed'))
|
||||
return { 'enabled': enabled }
|
||||
|
@ -441,6 +453,6 @@ def _update_firewall_file(rules):
|
|||
def _on_rule_command_error(returncode, cmd, output):
|
||||
"""Callback for rules commands error"""
|
||||
# Log error and continue commands execution
|
||||
logger.error('"%s" returned non-zero exit status %d:\n%s',
|
||||
cmd, returncode, prependlines(output.rstrip(), '> '))
|
||||
logger.info('"%s" returned non-zero exit status %d:\n%s',
|
||||
cmd, returncode, prependlines(output.rstrip(), '> '))
|
||||
return True
|
|
@ -29,14 +29,15 @@ import re
|
|||
import json
|
||||
import errno
|
||||
import subprocess
|
||||
from glob import iglob
|
||||
|
||||
from moulinette.core import MoulinetteError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils import log
|
||||
|
||||
hook_folder = '/usr/share/yunohost/hooks/'
|
||||
custom_hook_folder = '/etc/yunohost/hooks.d/'
|
||||
|
||||
logger = getActionLogger('yunohost.hook')
|
||||
logger = log.getActionLogger('yunohost.hook')
|
||||
|
||||
|
||||
def hook_add(app, file):
|
||||
|
@ -77,6 +78,46 @@ def hook_remove(app):
|
|||
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):
|
||||
"""
|
||||
List available hooks for an action
|
||||
|
@ -223,14 +264,9 @@ def hook_callback(action, hooks=[], args=None):
|
|||
state = 'succeed'
|
||||
filename = '%s-%s' % (priority, name)
|
||||
try:
|
||||
ret = hook_exec(info['path'], args=args)
|
||||
except:
|
||||
logger.exception("error while executing hook '%s'",
|
||||
info['path'])
|
||||
state = 'failed'
|
||||
if ret != 0:
|
||||
logger.error("error while executing hook '%s', retcode: %d",
|
||||
info['path'], ret)
|
||||
hook_exec(info['path'], args=args, raise_on_error=True)
|
||||
except MoulinetteError as e:
|
||||
logger.error(str(e))
|
||||
state = 'failed'
|
||||
try:
|
||||
result[state][name].append(info['path'])
|
||||
|
@ -239,111 +275,67 @@ def hook_callback(action, hooks=[], args=None):
|
|||
return result
|
||||
|
||||
|
||||
def hook_check(file):
|
||||
"""
|
||||
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):
|
||||
def hook_exec(path, args=None, raise_on_error=False, no_trace=False):
|
||||
"""
|
||||
Execute hook from a file with arguments
|
||||
|
||||
Keyword argument:
|
||||
file -- Script to execute
|
||||
args -- Arguments to pass to the script
|
||||
path -- Path of the script to execute
|
||||
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
|
||||
|
||||
if isinstance(args, list):
|
||||
arg_list = args
|
||||
else:
|
||||
required_args = hook_check(file)
|
||||
if args is None:
|
||||
args = {}
|
||||
# Validate hook path
|
||||
if path[0] != '/':
|
||||
path = os.path.realpath(path)
|
||||
if not os.path.isfile(path):
|
||||
raise MoulinetteError(errno.EIO, m18n.g('file_not_exist'))
|
||||
|
||||
arg_list = []
|
||||
for arg in required_args:
|
||||
if arg['name'] in args:
|
||||
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'])
|
||||
# Construct command variables
|
||||
cmd_fdir, cmd_fname = os.path.split(path)
|
||||
cmd_fname = './{0}'.format(cmd_fname)
|
||||
|
||||
# Append extra strings
|
||||
if 'choices' in arg:
|
||||
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:
|
||||
cmd_args = ''
|
||||
if args and isinstance(args, list):
|
||||
# Concatenate arguments and escape them with double quotes to prevent
|
||||
# bash related issue if an argument is empty and is not the last
|
||||
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(
|
||||
['sudo', '-u', 'admin', '-H', 'sh', '-c', 'cd "{:s}" && ' \
|
||||
'/bin/bash -x "{:s}" {:s}'.format(
|
||||
file_path, file, arg_str)],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
shell=False)
|
||||
if logger.isEnabledFor(log.DEBUG):
|
||||
logger.info(m18n.n('executing_command', command=' '.join(command)))
|
||||
else:
|
||||
logger.info(m18n.n('executing_script', script='{0}/{1}'.format(
|
||||
cmd_fdir, cmd_fname)))
|
||||
|
||||
# Wrap and get process ouput
|
||||
stream = NonBlockingStreamReader(p.stdout)
|
||||
while True:
|
||||
line = stream.readline(True, 0.1)
|
||||
if not line:
|
||||
# Check if process has terminated
|
||||
returncode = p.poll()
|
||||
if returncode is not None:
|
||||
break
|
||||
# Define output callbacks and call command
|
||||
callbacks = (
|
||||
lambda l: logger.info(l.rstrip()),
|
||||
lambda l: logger.warning(l.rstrip()),
|
||||
)
|
||||
returncode = call_async_output(command, callbacks, shell=False)
|
||||
|
||||
# Check and return process' return code
|
||||
if returncode is None:
|
||||
if raise_on_error:
|
||||
raise MoulinetteError(m18n.n('hook_exec_not_terminated'))
|
||||
else:
|
||||
msignals.display(line.rstrip(), 'log')
|
||||
stream.close()
|
||||
|
||||
logger.error(m18n.n('hook_exec_not_terminated'))
|
||||
return 1
|
||||
elif raise_on_error and returncode != 0:
|
||||
raise MoulinetteError(m18n.n('hook_exec_failed'))
|
||||
return returncode
|
||||
|
||||
|
|
@ -34,6 +34,7 @@ import difflib
|
|||
import hashlib
|
||||
|
||||
from moulinette.core import MoulinetteError
|
||||
from moulinette.utils import log
|
||||
|
||||
template_dir = os.getenv(
|
||||
'YUNOHOST_TEMPLATE_DIR',
|
||||
|
@ -44,6 +45,9 @@ conf_backup_dir = os.getenv(
|
|||
'/home/yunohost.backup/conffiles'
|
||||
)
|
||||
|
||||
logger = log.getActionLogger('yunohost.service')
|
||||
|
||||
|
||||
def service_add(name, status=None, log=None, runlevel=None):
|
||||
"""
|
||||
Add a custom service
|
||||
|
@ -73,7 +77,7 @@ def service_add(name, status=None, log=None, runlevel=None):
|
|||
except:
|
||||
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):
|
||||
|
@ -96,7 +100,7 @@ def service_remove(name):
|
|||
except:
|
||||
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):
|
||||
|
@ -111,12 +115,12 @@ def service_start(names):
|
|||
names = [names]
|
||||
for name in names:
|
||||
if _run_service_command('start', name):
|
||||
msignals.display(m18n.n('service_started', name), 'success')
|
||||
logger.success(m18n.n('service_started', name))
|
||||
else:
|
||||
if service_status(name)['status'] != 'running':
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
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):
|
||||
|
@ -131,12 +135,12 @@ def service_stop(names):
|
|||
names = [names]
|
||||
for name in names:
|
||||
if _run_service_command('stop', name):
|
||||
msignals.display(m18n.n('service_stopped', name), 'success')
|
||||
logger.success(m18n.n('service_stopped', name))
|
||||
else:
|
||||
if service_status(name)['status'] != 'inactive':
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
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):
|
||||
|
@ -151,7 +155,7 @@ def service_enable(names):
|
|||
names = [names]
|
||||
for name in names:
|
||||
if _run_service_command('enable', name):
|
||||
msignals.display(m18n.n('service_enabled', name), 'success')
|
||||
logger.success(m18n.n('service_enabled', name))
|
||||
else:
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_enable_failed', name))
|
||||
|
@ -169,7 +173,7 @@ def service_disable(names):
|
|||
names = [names]
|
||||
for name in names:
|
||||
if _run_service_command('disable', name):
|
||||
msignals.display(m18n.n('service_disabled', name), 'success')
|
||||
logger.success(m18n.n('service_disabled', name))
|
||||
else:
|
||||
raise MoulinetteError(errno.EPERM,
|
||||
m18n.n('service_disable_failed', name))
|
||||
|
@ -217,8 +221,7 @@ def service_status(names=[]):
|
|||
shell=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
if 'usage:' in e.output.lower():
|
||||
msignals.display(m18n.n('service_status_failed', name),
|
||||
'warning')
|
||||
logger.warning(m18n.n('service_status_failed', name))
|
||||
else:
|
||||
result[name]['status'] = 'inactive'
|
||||
else:
|
||||
|
@ -288,7 +291,7 @@ def service_regenconf(service=None, force=False):
|
|||
hook_callback('conf_regen', [service], args=[force])
|
||||
else:
|
||||
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):
|
||||
|
@ -317,8 +320,7 @@ def _run_service_command(action, service):
|
|||
ret = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
# TODO: Log output?
|
||||
msignals.display(m18n.n('service_cmd_exec_failed', ' '.join(e.cmd)),
|
||||
'warning')
|
||||
logger.warning(m18n.n('service_cmd_exec_failed', ' '.join(e.cmd)))
|
||||
return False
|
||||
return True
|
||||
|
||||
|
@ -467,12 +469,9 @@ def service_saferemove(service, conf_file, force=False):
|
|||
else:
|
||||
services[service]['conffiles'][conf_file] = previous_hash
|
||||
os.remove(conf_backup_file)
|
||||
if os.isatty(1) and \
|
||||
(len(previous_hash) == 32 or previous_hash[-32:] != current_hash):
|
||||
msignals.display(
|
||||
m18n.n('service_configuration_changed', conf_file),
|
||||
'warning'
|
||||
)
|
||||
if len(previous_hash) == 32 or previous_hash[-32:] != current_hash:
|
||||
logger.warning(m18n.n('service_configuration_conflict',
|
||||
file=conf_file))
|
||||
|
||||
_save_services(services)
|
||||
|
||||
|
@ -495,7 +494,7 @@ def service_safecopy(service, new_conf_file, conf_file, force=False):
|
|||
services = _get_services()
|
||||
|
||||
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:
|
||||
new_conf = ''.join(f.readlines()).rstrip()
|
||||
|
@ -509,8 +508,7 @@ def service_safecopy(service, new_conf_file, conf_file, force=False):
|
|||
)
|
||||
process.wait()
|
||||
else:
|
||||
msignals.display(m18n.n('service_add_configuration', conf_file),
|
||||
'info')
|
||||
logger.info(m18n.n('service_add_configuration', file=conf_file))
|
||||
|
||||
# Add the service if it does not exist
|
||||
if service not in services.keys():
|
||||
|
@ -539,14 +537,9 @@ def service_safecopy(service, new_conf_file, conf_file, force=False):
|
|||
else:
|
||||
new_hash = previous_hash
|
||||
if (len(previous_hash) == 32 or previous_hash[-32:] != current_hash):
|
||||
msignals.display(
|
||||
m18n.n('service_configuration_conflict', conf_file),
|
||||
'warning'
|
||||
)
|
||||
print('\n' + conf_file)
|
||||
for line in diff:
|
||||
print(line.strip())
|
||||
print('')
|
||||
logger.warning('{0} {1}'.format(
|
||||
m18n.n('service_configuration_conflict', file=conf_file),
|
||||
m18n.n('show_diff', diff=''.join(diff))))
|
||||
|
||||
# Remove the backup file if the configuration has not changed
|
||||
if new_hash == previous_hash:
|
|
@ -269,13 +269,13 @@ def tools_postinstall(domain, password, ignore_dyndns=False):
|
|||
|
||||
# Enable UPnP silently and reload firewall
|
||||
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')
|
||||
|
||||
# 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)
|
||||
|
||||
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 is_api:
|
||||
critical_packages = ("moulinette", "moulinette-yunohost",
|
||||
"yunohost-admin", "yunohost-config-nginx", "ssowat", "python")
|
||||
critical_packages = ("moulinette", "yunohost",
|
||||
"yunohost-admin", "ssowat", "python")
|
||||
critical_upgrades = set()
|
||||
|
||||
for pkg in cache.get_changes():
|
Loading…
Add table
Reference in a new issue