mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge branch 'dev' into enh-config-panel-file
This commit is contained in:
commit
8e2cf58862
58 changed files with 1381 additions and 448 deletions
|
@ -181,3 +181,12 @@ test-service:
|
|||
only:
|
||||
changes:
|
||||
- src/yunohost/service.py
|
||||
|
||||
test-ldapauth:
|
||||
extends: .test-stage
|
||||
script:
|
||||
- cd src/yunohost
|
||||
- python3 -m pytest tests/test_ldapauth.py
|
||||
only:
|
||||
changes:
|
||||
- src/yunohost/authenticators/*.py
|
||||
|
|
173
bin/yunomdns
Executable file
173
bin/yunomdns
Executable file
|
@ -0,0 +1,173 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Pythonic declaration of mDNS .local domains for YunoHost
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import re
|
||||
import sys
|
||||
import yaml
|
||||
|
||||
import socket
|
||||
from time import sleep
|
||||
from typing import List, Dict
|
||||
|
||||
from zeroconf import Zeroconf, ServiceInfo
|
||||
|
||||
# Helper command taken from Moulinette
|
||||
def check_output(args, stderr=subprocess.STDOUT, shell=True, **kwargs):
|
||||
"""Run command with arguments and return its output as a byte string
|
||||
Overwrite some of the arguments to capture standard error in the result
|
||||
and use shell by default before calling subprocess.check_output.
|
||||
"""
|
||||
return (
|
||||
subprocess.check_output(args, stderr=stderr, shell=shell, **kwargs)
|
||||
.decode("utf-8")
|
||||
.strip()
|
||||
)
|
||||
|
||||
# Helper command taken from Moulinette
|
||||
def _extract_inet(string, skip_netmask=False, skip_loopback=True):
|
||||
"""
|
||||
Extract IP addresses (v4 and/or v6) from a string limited to one
|
||||
address by protocol
|
||||
|
||||
Keyword argument:
|
||||
string -- String to search in
|
||||
skip_netmask -- True to skip subnet mask extraction
|
||||
skip_loopback -- False to include addresses reserved for the
|
||||
loopback interface
|
||||
|
||||
Returns:
|
||||
A dict of {protocol: address} with protocol one of 'ipv4' or 'ipv6'
|
||||
|
||||
"""
|
||||
ip4_pattern = (
|
||||
r"((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}"
|
||||
)
|
||||
ip6_pattern = r"(((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::?((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)"
|
||||
ip4_pattern += r"/[0-9]{1,2})" if not skip_netmask else ")"
|
||||
ip6_pattern += r"/[0-9]{1,3})" if not skip_netmask else ")"
|
||||
result = {}
|
||||
|
||||
for m in re.finditer(ip4_pattern, string):
|
||||
addr = m.group(1)
|
||||
if skip_loopback and addr.startswith("127."):
|
||||
continue
|
||||
|
||||
# Limit to only one result
|
||||
result["ipv4"] = addr
|
||||
break
|
||||
|
||||
for m in re.finditer(ip6_pattern, string):
|
||||
addr = m.group(1)
|
||||
if skip_loopback and addr == "::1":
|
||||
continue
|
||||
|
||||
# Limit to only one result
|
||||
result["ipv6"] = addr
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
# Helper command taken from Moulinette
|
||||
def get_network_interfaces():
|
||||
|
||||
# Get network devices and their addresses (raw infos from 'ip addr')
|
||||
devices_raw = {}
|
||||
output = check_output("ip --brief a").split("\n")
|
||||
for line in output:
|
||||
line = line.split()
|
||||
iname = line[0]
|
||||
ips = ' '.join(line[2:])
|
||||
|
||||
devices_raw[iname] = ips
|
||||
|
||||
# Parse relevant informations for each of them
|
||||
devices = {
|
||||
name: _extract_inet(addrs)
|
||||
for name, addrs in devices_raw.items()
|
||||
if name != "lo"
|
||||
}
|
||||
|
||||
return devices
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
###
|
||||
# CONFIG
|
||||
###
|
||||
|
||||
with open('/etc/yunohost/mdns.yml', 'r') as f:
|
||||
config = yaml.safe_load(f) or {}
|
||||
updated = False
|
||||
|
||||
required_fields = ["interfaces", "domains"]
|
||||
missing_fields = [field for field in required_fields if field not in config]
|
||||
|
||||
if missing_fields:
|
||||
print("The fields %s are required" % ', '.join(missing_fields))
|
||||
|
||||
if config['interfaces'] is None:
|
||||
print('No interface listed for broadcast.')
|
||||
sys.exit(0)
|
||||
|
||||
if 'yunohost.local' not in config['domains']:
|
||||
config['domains'].append('yunohost.local')
|
||||
|
||||
zcs = {}
|
||||
interfaces = get_network_interfaces()
|
||||
for interface in config['interfaces']:
|
||||
infos = [] # List of ServiceInfo objects, to feed Zeroconf
|
||||
ips = [] # Human-readable IPs
|
||||
b_ips = [] # Binary-convered IPs
|
||||
|
||||
ipv4 = interfaces[interface]['ipv4'].split('/')[0]
|
||||
if ipv4:
|
||||
ips.append(ipv4)
|
||||
b_ips.append(socket.inet_pton(socket.AF_INET, ipv4))
|
||||
|
||||
ipv6 = interfaces[interface]['ipv6'].split('/')[0]
|
||||
if ipv6:
|
||||
ips.append(ipv6)
|
||||
b_ips.append(socket.inet_pton(socket.AF_INET6, ipv6))
|
||||
|
||||
# If at least one IP is listed
|
||||
if ips:
|
||||
# Create a Zeroconf object, and store the ServiceInfos
|
||||
zc = Zeroconf(interfaces=ips)
|
||||
zcs[zc]=[]
|
||||
for d in config['domains']:
|
||||
d_domain=d.replace('.local','')
|
||||
if '.' in d_domain:
|
||||
print(d_domain+'.local: subdomains are not supported.')
|
||||
else:
|
||||
# Create a ServiceInfo object for each .local domain
|
||||
zcs[zc].append(ServiceInfo(
|
||||
type_='_device-info._tcp.local.',
|
||||
name=interface+': '+d_domain+'._device-info._tcp.local.',
|
||||
addresses=b_ips,
|
||||
port=80,
|
||||
server=d+'.',
|
||||
))
|
||||
print('Adding '+d+' with addresses '+str(ips)+' on interface '+interface)
|
||||
|
||||
# Run registration
|
||||
print("Registering...")
|
||||
for zc, infos in zcs.items():
|
||||
for info in infos:
|
||||
zc.register_service(info)
|
||||
|
||||
try:
|
||||
print("Registered. Press Ctrl+C or stop service to stop.")
|
||||
while True:
|
||||
sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
print("Unregistering...")
|
||||
for zc, infos in zcs.items():
|
||||
for info in infos:
|
||||
zc.unregister_service(info)
|
||||
zc.close()
|
|
@ -33,18 +33,10 @@
|
|||
# Global parameters #
|
||||
#############################
|
||||
_global:
|
||||
configuration:
|
||||
authenticate:
|
||||
- api
|
||||
authenticator:
|
||||
default:
|
||||
vendor: ldap
|
||||
help: admin_password
|
||||
parameters:
|
||||
uri: ldap://localhost:389
|
||||
base_dn: dc=yunohost,dc=org
|
||||
user_rdn: cn=admin,dc=yunohost,dc=org
|
||||
argument_auth: false
|
||||
name: yunohost.admin
|
||||
authentication:
|
||||
api: ldap_admin
|
||||
cli: null
|
||||
arguments:
|
||||
-v:
|
||||
full: --version
|
||||
|
@ -673,7 +665,11 @@ app:
|
|||
api: DELETE /apps/<app>
|
||||
arguments:
|
||||
app:
|
||||
help: App to delete
|
||||
help: App to remove
|
||||
-p:
|
||||
full: --purge
|
||||
help: Also remove all application data
|
||||
action: store_true
|
||||
|
||||
### app_upgrade()
|
||||
upgrade:
|
||||
|
@ -693,6 +689,10 @@ app:
|
|||
full: --force
|
||||
help: Force the update, even though the app is up to date
|
||||
action: store_true
|
||||
-b:
|
||||
full: --no-safety-backup
|
||||
help: Disable the safety backup during upgrade
|
||||
action: store_true
|
||||
|
||||
### app_change_url()
|
||||
change-url:
|
||||
|
@ -1417,9 +1417,9 @@ tools:
|
|||
postinstall:
|
||||
action_help: YunoHost post-install
|
||||
api: POST /postinstall
|
||||
configuration:
|
||||
authentication:
|
||||
# We need to be able to run the postinstall without being authenticated, otherwise we can't run the postinstall
|
||||
authenticate: false
|
||||
api: null
|
||||
arguments:
|
||||
-d:
|
||||
full: --domain
|
||||
|
|
|
@ -32,7 +32,7 @@ def get_dict_actions(OPTION_SUBTREE, category):
|
|||
with open(ACTIONSMAP_FILE, "r") as stream:
|
||||
|
||||
# Getting the dictionary containning what actions are possible per category
|
||||
OPTION_TREE = yaml.load(stream)
|
||||
OPTION_TREE = yaml.safe_load(stream)
|
||||
|
||||
CATEGORY = [
|
||||
category for category in OPTION_TREE.keys() if not category.startswith("_")
|
||||
|
|
|
@ -31,7 +31,11 @@ ynh_multimedia_build_main_dir() {
|
|||
mkdir -p "$MEDIA_DIRECTORY/$user/eBook"
|
||||
ln -sfn "$MEDIA_DIRECTORY/share" "$MEDIA_DIRECTORY/$user/Share"
|
||||
# Création du lien symbolique dans le home de l'utilisateur.
|
||||
ln -sfn "$MEDIA_DIRECTORY/$user" "/home/$user/Multimedia"
|
||||
#link will only be created if the home directory of the user exists and if it's located in '/home' folder
|
||||
local user_home="$(getent passwd $user | cut -d: -f6 | grep '^/home/')"
|
||||
if [[ -d "$user_home" ]]; then
|
||||
ln -sfn "$MEDIA_DIRECTORY/$user" "$user_home/Multimedia"
|
||||
fi
|
||||
# Propriétaires des dossiers utilisateurs.
|
||||
chown -R $user "$MEDIA_DIRECTORY/$user"
|
||||
done
|
||||
|
|
|
@ -86,7 +86,7 @@ key, value = os.environ['KEY'], os.environ.get('VALUE', None)
|
|||
setting_file = "/etc/yunohost/apps/%s/settings.yml" % app
|
||||
assert os.path.exists(setting_file), "Setting file %s does not exists ?" % setting_file
|
||||
with open(setting_file) as f:
|
||||
settings = yaml.load(f)
|
||||
settings = yaml.safe_load(f)
|
||||
if action == "get":
|
||||
if key in settings:
|
||||
print(settings[key])
|
||||
|
@ -96,7 +96,7 @@ else:
|
|||
del settings[key]
|
||||
elif action == "set":
|
||||
if key in ['redirected_urls', 'redirected_regex']:
|
||||
value = yaml.load(value)
|
||||
value = yaml.safe_load(value)
|
||||
settings[key] = value
|
||||
else:
|
||||
raise ValueError("action should either be get, set or delete")
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
set -e
|
||||
|
||||
services_path="/etc/yunohost/services.yml"
|
||||
|
||||
do_init_regen() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "You must be root to run this script" 1>&2
|
||||
|
@ -19,8 +17,6 @@ do_init_regen() {
|
|||
|| echo "yunohost.org" > /etc/yunohost/current_host
|
||||
|
||||
# copy default services and firewall
|
||||
[[ -f $services_path ]] \
|
||||
|| cp services.yml "$services_path"
|
||||
[[ -f /etc/yunohost/firewall.yml ]] \
|
||||
|| cp firewall.yml /etc/yunohost/firewall.yml
|
||||
|
||||
|
@ -49,6 +45,9 @@ do_init_regen() {
|
|||
chmod 644 /etc/ssowat/conf.json.persistent
|
||||
chown root:root /etc/ssowat/conf.json.persistent
|
||||
|
||||
# Empty service conf
|
||||
touch /etc/yunohost/services.yml
|
||||
|
||||
mkdir -p /var/cache/yunohost/repo
|
||||
chown root:root /var/cache/yunohost
|
||||
chmod 700 /var/cache/yunohost
|
||||
|
@ -59,25 +58,9 @@ do_pre_regen() {
|
|||
|
||||
cd /usr/share/yunohost/templates/yunohost
|
||||
|
||||
# update services.yml
|
||||
if [[ -f $services_path ]]; then
|
||||
tmp_services_path="${services_path}-tmp"
|
||||
new_services_path="${services_path}-new"
|
||||
cp "$services_path" "$tmp_services_path"
|
||||
_update_services "$new_services_path" || {
|
||||
mv "$tmp_services_path" "$services_path"
|
||||
exit 1
|
||||
}
|
||||
if [[ -f $new_services_path ]]; then
|
||||
# replace services.yml with new one
|
||||
mv "$new_services_path" "$services_path"
|
||||
mv "$tmp_services_path" "${services_path}-old"
|
||||
else
|
||||
rm -f "$tmp_services_path"
|
||||
fi
|
||||
else
|
||||
cp services.yml /etc/yunohost/services.yml
|
||||
fi
|
||||
# Legacy code that can be removed once on bullseye
|
||||
touch /etc/yunohost/services.yml
|
||||
yunohost tools shell -c "from yunohost.service import _get_services, _save_services; _save_services(_get_services())"
|
||||
|
||||
mkdir -p $pending_dir/etc/cron.d/
|
||||
mkdir -p $pending_dir/etc/cron.daily/
|
||||
|
@ -145,6 +128,14 @@ HandleLidSwitchDocked=ignore
|
|||
HandleLidSwitchExternalPower=ignore
|
||||
EOF
|
||||
|
||||
mkdir -p ${pending_dir}/etc/systemd/
|
||||
if [[ "$(yunohost settings get 'security.experimental.enabled')" == "True" ]]
|
||||
then
|
||||
cp proc-hidepid.service ${pending_dir}/etc/systemd/system/proc-hidepid.service
|
||||
else
|
||||
touch ${pending_dir}/etc/systemd/system/proc-hidepid.service
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
do_post_regen() {
|
||||
|
@ -204,65 +195,13 @@ do_post_regen() {
|
|||
# Propagates changes in systemd service config overrides
|
||||
[[ ! "$regen_conf_files" =~ "ntp.service.d/ynh-override.conf" ]] || { systemctl daemon-reload; systemctl restart ntp; }
|
||||
[[ ! "$regen_conf_files" =~ "nftables.service.d/ynh-override.conf" ]] || systemctl daemon-reload
|
||||
}
|
||||
|
||||
_update_services() {
|
||||
python3 - << EOF
|
||||
import yaml
|
||||
|
||||
|
||||
with open('services.yml') as f:
|
||||
new_services = yaml.load(f)
|
||||
|
||||
with open('/etc/yunohost/services.yml') as f:
|
||||
services = yaml.load(f) or {}
|
||||
|
||||
updated = False
|
||||
|
||||
|
||||
for service, conf in new_services.items():
|
||||
# remove service with empty conf
|
||||
if conf is None:
|
||||
if service in services:
|
||||
print("removing '{0}' from services".format(service))
|
||||
del services[service]
|
||||
updated = True
|
||||
|
||||
# add new service
|
||||
elif not services.get(service, None):
|
||||
print("adding '{0}' to services".format(service))
|
||||
services[service] = conf
|
||||
updated = True
|
||||
|
||||
# update service conf
|
||||
else:
|
||||
conffiles = services[service].pop('conffiles', {})
|
||||
|
||||
# status need to be removed
|
||||
if "status" not in conf and "status" in services[service]:
|
||||
print("update '{0}' service status access".format(service))
|
||||
del services[service]["status"]
|
||||
updated = True
|
||||
|
||||
if services[service] != conf:
|
||||
print("update '{0}' service".format(service))
|
||||
services[service].update(conf)
|
||||
updated = True
|
||||
|
||||
if conffiles:
|
||||
services[service]['conffiles'] = conffiles
|
||||
|
||||
# Remove legacy /var/log/daemon.log and /var/log/syslog from log entries
|
||||
# because they are too general. Instead, now the journalctl log is
|
||||
# returned by default which is more relevant.
|
||||
if "log" in services[service]:
|
||||
if services[service]["log"] in ["/var/log/syslog", "/var/log/daemon.log"]:
|
||||
del services[service]["log"]
|
||||
|
||||
if updated:
|
||||
with open('/etc/yunohost/services.yml-new', 'w') as f:
|
||||
yaml.safe_dump(services, f, default_flow_style=False)
|
||||
EOF
|
||||
[[ ! "$regen_conf_files" =~ "login.conf.d/ynh-override.conf" ]] || systemctl daemon-reload
|
||||
if [[ "$regen_conf_files" =~ "proc-hidepid.service" ]]
|
||||
then
|
||||
systemctl daemon-reload
|
||||
action=$([[ -e /etc/systemd/system/proc-hidepid.service ]] && echo 'enable' || echo 'disable')
|
||||
systemctl $action proc-hidepid --quiet --now
|
||||
fi
|
||||
}
|
||||
|
||||
FORCE=${2:-0}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
set -e
|
||||
|
||||
tmp_backup_dir_file="/tmp/slapd-backup-dir.txt"
|
||||
tmp_backup_dir_file="/root/slapd-backup-dir.txt"
|
||||
|
||||
config="/usr/share/yunohost/templates/slapd/config.ldif"
|
||||
db_init="/usr/share/yunohost/templates/slapd/db_init.ldif"
|
||||
|
|
|
@ -17,19 +17,16 @@ Pin-Priority: -1" >> "${pending_dir}/etc/apt/preferences.d/extra_php_version"
|
|||
done
|
||||
|
||||
echo "
|
||||
# Yes !
|
||||
# This is what's preventing you from installing apache2 !
|
||||
#
|
||||
# Maybe take two fucking minutes to realize that if you try to install
|
||||
# apache2, this will break nginx and break the entire YunoHost ecosystem.
|
||||
# on your server.
|
||||
#
|
||||
# So, *NO*
|
||||
# DO NOT do this.
|
||||
# DO NOT remove these lines.
|
||||
#
|
||||
# I warned you. I WARNED YOU! But did you listen to me?
|
||||
# Oooooh, noooo. You knew it all, didn't you?
|
||||
|
||||
# PLEASE READ THIS WARNING AND DON'T EDIT THIS FILE
|
||||
|
||||
# You are probably reading this file because you tried to install apache2 or
|
||||
# bind9. These 2 packages conflict with YunoHost.
|
||||
|
||||
# Installing apache2 will break nginx and break the entire YunoHost ecosystem
|
||||
# on your server, therefore don't remove those lines!
|
||||
|
||||
# You have been warned.
|
||||
|
||||
Package: apache2
|
||||
Pin: release *
|
||||
|
@ -39,9 +36,9 @@ Package: apache2-bin
|
|||
Pin: release *
|
||||
Pin-Priority: -1
|
||||
|
||||
# Also yes, bind9 will conflict with dnsmasq.
|
||||
# Same story than for apache2.
|
||||
# Don't fucking install it.
|
||||
# Also bind9 will conflict with dnsmasq.
|
||||
# Same story as for apache2.
|
||||
# Don't install it, don't remove those lines.
|
||||
|
||||
Package: bind9
|
||||
Pin: release *
|
||||
|
|
|
@ -61,6 +61,7 @@ do_pre_regen() {
|
|||
|
||||
# Support different strategy for security configurations
|
||||
export compatibility="$(yunohost settings get 'security.nginx.compatibility')"
|
||||
export experimental="$(yunohost settings get 'security.experimental.enabled')"
|
||||
ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc"
|
||||
|
||||
cert_status=$(yunohost domain cert-status --json)
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
do_pre_regen() {
|
||||
pending_dir=$1
|
||||
|
||||
cd /usr/share/yunohost/templates/avahi-daemon
|
||||
|
||||
install -D -m 644 avahi-daemon.conf \
|
||||
"${pending_dir}/etc/avahi/avahi-daemon.conf"
|
||||
}
|
||||
|
||||
do_post_regen() {
|
||||
regen_conf_files=$1
|
||||
|
||||
[[ -z "$regen_conf_files" ]] \
|
||||
|| systemctl restart avahi-daemon
|
||||
}
|
||||
|
||||
FORCE=${2:-0}
|
||||
DRY_RUN=${3:-0}
|
||||
|
||||
case "$1" in
|
||||
pre)
|
||||
do_pre_regen $4
|
||||
;;
|
||||
post)
|
||||
do_post_regen $4
|
||||
;;
|
||||
*)
|
||||
echo "hook called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
83
data/hooks/conf_regen/37-mdns
Executable file
83
data/hooks/conf_regen/37-mdns
Executable file
|
@ -0,0 +1,83 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
_generate_config() {
|
||||
echo "domains:"
|
||||
echo " - yunohost.local"
|
||||
for domain in $YNH_DOMAINS
|
||||
do
|
||||
# Only keep .local domains (don't keep
|
||||
[[ "$domain" =~ [^.]+\.[^.]+\.local$ ]] && echo "Subdomain $domain cannot be handled by Bonjour/Zeroconf/mDNS" >&2
|
||||
[[ "$domain" =~ ^[^.]+\.local$ ]] || continue
|
||||
echo " - $domain"
|
||||
done
|
||||
|
||||
echo "interfaces:"
|
||||
local_network_interfaces="$(ip --brief a | grep ' 10\.\| 192\.168\.' | awk '{print $1}')"
|
||||
for interface in $local_network_interfaces
|
||||
do
|
||||
echo " - $interface"
|
||||
done
|
||||
}
|
||||
|
||||
do_init_regen() {
|
||||
do_pre_regen
|
||||
do_post_regen /etc/systemd/system/yunomdns.service
|
||||
systemctl enable yunomdns
|
||||
}
|
||||
|
||||
do_pre_regen() {
|
||||
pending_dir="$1"
|
||||
|
||||
cd /usr/share/yunohost/templates/mdns
|
||||
mkdir -p ${pending_dir}/etc/systemd/system/
|
||||
cp yunomdns.service ${pending_dir}/etc/systemd/system/
|
||||
|
||||
getent passwd mdns &>/dev/null || useradd --no-create-home --shell /usr/sbin/nologin --system --user-group mdns
|
||||
|
||||
mkdir -p ${pending_dir}/etc/yunohost
|
||||
_generate_config > ${pending_dir}/etc/yunohost/mdns.yml
|
||||
}
|
||||
|
||||
do_post_regen() {
|
||||
regen_conf_files="$1"
|
||||
|
||||
chown mdns:mdns /etc/yunohost/mdns.yml
|
||||
|
||||
# If we changed the systemd ynh-override conf
|
||||
if echo "$regen_conf_files" | sed 's/,/\n/g' | grep -q "^/etc/systemd/system/yunomdns.service$"
|
||||
then
|
||||
systemctl daemon-reload
|
||||
fi
|
||||
|
||||
# Legacy stuff to enable the new yunomdns service on legacy systems
|
||||
if [[ -e /etc/avahi/avahi-daemon.conf ]] && grep -q 'yunohost' /etc/avahi/avahi-daemon.conf
|
||||
then
|
||||
systemctl enable yunomdns
|
||||
fi
|
||||
|
||||
[[ -z "$regen_conf_files" ]] \
|
||||
|| systemctl restart yunomdns
|
||||
}
|
||||
|
||||
FORCE=${2:-0}
|
||||
DRY_RUN=${3:-0}
|
||||
|
||||
case "$1" in
|
||||
pre)
|
||||
do_pre_regen $4
|
||||
;;
|
||||
post)
|
||||
do_post_regen $4
|
||||
;;
|
||||
init)
|
||||
do_init_regen
|
||||
;;
|
||||
*)
|
||||
echo "hook called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
|
@ -13,6 +13,7 @@ from yunohost.diagnosis import Diagnoser
|
|||
from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain
|
||||
|
||||
YNH_DYNDNS_DOMAINS = ["nohost.me", "noho.st", "ynh.fr"]
|
||||
SPECIAL_USE_TLDS = ["local", "localhost", "onion", "test"]
|
||||
|
||||
|
||||
class DNSRecordsDiagnoser(Diagnoser):
|
||||
|
@ -29,8 +30,14 @@ class DNSRecordsDiagnoser(Diagnoser):
|
|||
for domain in all_domains:
|
||||
self.logger_debug("Diagnosing DNS conf for %s" % domain)
|
||||
is_subdomain = domain.split(".", 1)[1] in all_domains
|
||||
is_specialusedomain = any(
|
||||
domain.endswith("." + tld) for tld in SPECIAL_USE_TLDS
|
||||
)
|
||||
for report in self.check_domain(
|
||||
domain, domain == main_domain, is_subdomain=is_subdomain
|
||||
domain,
|
||||
domain == main_domain,
|
||||
is_subdomain=is_subdomain,
|
||||
is_specialusedomain=is_specialusedomain,
|
||||
):
|
||||
yield report
|
||||
|
||||
|
@ -48,7 +55,7 @@ class DNSRecordsDiagnoser(Diagnoser):
|
|||
for report in self.check_expiration_date(domains_from_registrar):
|
||||
yield report
|
||||
|
||||
def check_domain(self, domain, is_main_domain, is_subdomain):
|
||||
def check_domain(self, domain, is_main_domain, is_subdomain, is_specialusedomain):
|
||||
|
||||
expected_configuration = _build_dns_conf(
|
||||
domain, include_empty_AAAA_if_no_ipv6=True
|
||||
|
@ -59,6 +66,15 @@ class DNSRecordsDiagnoser(Diagnoser):
|
|||
if is_subdomain:
|
||||
categories = ["basic"]
|
||||
|
||||
if is_specialusedomain:
|
||||
categories = []
|
||||
yield dict(
|
||||
meta={"domain": domain},
|
||||
data={},
|
||||
status="INFO",
|
||||
summary="diagnosis_dns_specialusedomain",
|
||||
)
|
||||
|
||||
for category in categories:
|
||||
|
||||
records = expected_configuration[category]
|
||||
|
|
|
@ -34,6 +34,12 @@ class WebDiagnoser(Diagnoser):
|
|||
summary="diagnosis_http_nginx_conf_not_up_to_date",
|
||||
details=["diagnosis_http_nginx_conf_not_up_to_date_details"],
|
||||
)
|
||||
elif domain.endswith(".local"):
|
||||
yield dict(
|
||||
meta={"domain": domain},
|
||||
status="INFO",
|
||||
summary="diagnosis_http_localdomain",
|
||||
)
|
||||
else:
|
||||
domains_to_check.append(domain)
|
||||
|
||||
|
|
76
data/hooks/diagnosis/80-apps.py
Normal file
76
data/hooks/diagnosis/80-apps.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
|
||||
from yunohost.app import app_list
|
||||
|
||||
from yunohost.diagnosis import Diagnoser
|
||||
|
||||
class AppDiagnoser(Diagnoser):
|
||||
|
||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||
cache_duration = 300
|
||||
dependencies = []
|
||||
|
||||
def run(self):
|
||||
|
||||
apps = app_list(full=True)["apps"]
|
||||
for app in apps:
|
||||
app["issues"] = list(self.issues(app))
|
||||
|
||||
if not any(app["issues"] for app in apps):
|
||||
yield dict(
|
||||
meta={"test": "apps"},
|
||||
status="SUCCESS",
|
||||
summary="diagnosis_apps_allgood",
|
||||
)
|
||||
else:
|
||||
for app in apps:
|
||||
|
||||
if not app["issues"]:
|
||||
continue
|
||||
|
||||
level = "ERROR" if any(issue[0] == "error" for issue in app["issues"]) else "WARNING"
|
||||
|
||||
yield dict(
|
||||
meta={"test": "apps", "app": app["name"]},
|
||||
status=level,
|
||||
summary="diagnosis_apps_issue",
|
||||
details=[issue[1] for issue in app["issues"]]
|
||||
)
|
||||
|
||||
def issues(self, app):
|
||||
|
||||
# Check quality level in catalog
|
||||
|
||||
if not app.get("from_catalog") or app["from_catalog"].get("state") != "working":
|
||||
yield ("error", "diagnosis_apps_not_in_app_catalog")
|
||||
elif not isinstance(app["from_catalog"].get("level"), int) or app["from_catalog"]["level"] == 0:
|
||||
yield ("error", "diagnosis_apps_broken")
|
||||
elif app["from_catalog"]["level"] <= 4:
|
||||
yield ("warning", "diagnosis_apps_bad_quality")
|
||||
|
||||
# Check for super old, deprecated practices
|
||||
|
||||
yunohost_version_req = app["manifest"].get("requirements", {}).get("yunohost", "").strip(">= ")
|
||||
if yunohost_version_req.startswith("2."):
|
||||
yield ("error", "diagnosis_apps_outdated_ynh_requirement")
|
||||
|
||||
deprecated_helpers = [
|
||||
"yunohost app setting",
|
||||
"yunohost app checkurl",
|
||||
"yunohost app checkport",
|
||||
"yunohost app initdb",
|
||||
"yunohost tools port-available",
|
||||
]
|
||||
for deprecated_helper in deprecated_helpers:
|
||||
if os.system(f"grep -nr -q '{deprecated_helper}' {app['setting_path']}/scripts/") == 0:
|
||||
yield ("error", "diagnosis_apps_deprecated_practices")
|
||||
|
||||
old_arg_regex = r'^domain=\${?[0-9]'
|
||||
if os.system(f"grep -q '{old_arg_regex}' {app['setting_path']}/scripts/install") == 0:
|
||||
yield ("error", "diagnosis_apps_deprecated_practices")
|
||||
|
||||
|
||||
def main(args, env, loggers):
|
||||
return AppDiagnoser(args, env, loggers).diagnose()
|
|
@ -15,7 +15,11 @@ mkdir -p "$MEDIA_DIRECTORY/$user/Video"
|
|||
mkdir -p "$MEDIA_DIRECTORY/$user/eBook"
|
||||
ln -sfn "$MEDIA_DIRECTORY/share" "$MEDIA_DIRECTORY/$user/Share"
|
||||
# Création du lien symbolique dans le home de l'utilisateur.
|
||||
ln -sfn "$MEDIA_DIRECTORY/$user" "/home/$user/Multimedia"
|
||||
#link will only be created if the home directory of the user exists and if it's located in '/home' folder
|
||||
user_home="$(getent passwd $user | cut -d: -f6 | grep '^/home/')"
|
||||
if [[ -d "$user_home" ]]; then
|
||||
ln -sfn "$MEDIA_DIRECTORY/$user" "$user_home/Multimedia"
|
||||
fi
|
||||
# Propriétaires des dossiers utilisateurs.
|
||||
chown -R $user "$MEDIA_DIRECTORY/$user"
|
||||
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
# This file is part of avahi.
|
||||
#
|
||||
# avahi is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation; either version 2 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# avahi is distributed in the hope that it will be useful, but WITHOUT
|
||||
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
|
||||
# License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with avahi; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
# USA.
|
||||
|
||||
# See avahi-daemon.conf(5) for more information on this configuration
|
||||
# file!
|
||||
|
||||
[server]
|
||||
host-name=yunohost
|
||||
domain-name=local
|
||||
#browse-domains=0pointer.de, zeroconf.org
|
||||
use-ipv4=yes
|
||||
use-ipv6=yes
|
||||
#allow-interfaces=eth0
|
||||
#deny-interfaces=eth1
|
||||
#check-response-ttl=no
|
||||
#use-iff-running=no
|
||||
#enable-dbus=yes
|
||||
#disallow-other-stacks=no
|
||||
#allow-point-to-point=no
|
||||
#cache-entries-max=4096
|
||||
#clients-max=4096
|
||||
#objects-per-client-max=1024
|
||||
#entries-per-entry-group-max=32
|
||||
ratelimit-interval-usec=1000000
|
||||
ratelimit-burst=1000
|
||||
|
||||
[wide-area]
|
||||
enable-wide-area=yes
|
||||
|
||||
[publish]
|
||||
#disable-publishing=no
|
||||
#disable-user-service-publishing=no
|
||||
#add-service-cookie=no
|
||||
#publish-addresses=yes
|
||||
#publish-hinfo=yes
|
||||
#publish-workstation=yes
|
||||
#publish-domain=yes
|
||||
#publish-dns-servers=192.168.50.1, 192.168.50.2
|
||||
#publish-resolv-conf-dns-servers=yes
|
||||
#publish-aaaa-on-ipv4=yes
|
||||
#publish-a-on-ipv6=no
|
||||
|
||||
[reflector]
|
||||
#enable-reflector=no
|
||||
#reflect-ipv=no
|
||||
|
||||
[rlimits]
|
||||
#rlimit-as=
|
||||
rlimit-core=0
|
||||
rlimit-data=4194304
|
||||
rlimit-fsize=0
|
||||
rlimit-nofile=768
|
||||
rlimit-stack=4194304
|
||||
rlimit-nproc=3
|
13
data/templates/mdns/yunomdns.service
Normal file
13
data/templates/mdns/yunomdns.service
Normal file
|
@ -0,0 +1,13 @@
|
|||
[Unit]
|
||||
Description=YunoHost mDNS service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=mdns
|
||||
Group=mdns
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/yunomdns
|
||||
StandardOutput=syslog
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
|
@ -30,7 +30,7 @@ skip-external-locking
|
|||
key_buffer_size = 16K
|
||||
max_allowed_packet = 16M
|
||||
table_open_cache = 4
|
||||
sort_buffer_size = 64K
|
||||
sort_buffer_size = 256K
|
||||
read_buffer_size = 256K
|
||||
read_rnd_buffer_size = 256K
|
||||
net_buffer_length = 2K
|
||||
|
|
|
@ -25,7 +25,11 @@ ssl_dhparam /usr/share/yunohost/other/ffdhe2048.pem;
|
|||
# Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners
|
||||
# https://wiki.mozilla.org/Security/Guidelines/Web_Security
|
||||
# https://observatory.mozilla.org/
|
||||
{% if experimental == "True" %}
|
||||
more_set_headers "Content-Security-Policy : upgrade-insecure-requests; default-src https: data:";
|
||||
{% else %}
|
||||
more_set_headers "Content-Security-Policy : upgrade-insecure-requests";
|
||||
{% endif %}
|
||||
more_set_headers "Content-Security-Policy-Report-Only : default-src https: data: 'unsafe-inline' 'unsafe-eval' ";
|
||||
more_set_headers "X-Content-Type-Options : nosniff";
|
||||
more_set_headers "X-XSS-Protection : 1; mode=block";
|
||||
|
@ -34,7 +38,13 @@ more_set_headers "X-Permitted-Cross-Domain-Policies : none";
|
|||
more_set_headers "X-Frame-Options : SAMEORIGIN";
|
||||
|
||||
# Disable the disaster privacy thing that is FLoC
|
||||
{% if experimental == "True" %}
|
||||
more_set_headers "Permissions-Policy : fullscreen=(), geolocation=(), payment=(), accelerometer=(), battery=(), magnetometer=(), usb=(), interest-cohort=()";
|
||||
# Force HTTPOnly and Secure for all cookies
|
||||
proxy_cookie_path ~$ "; HTTPOnly; Secure;";
|
||||
{% else %}
|
||||
more_set_headers "Permissions-Policy : interest-cohort=()";
|
||||
{% endif %}
|
||||
|
||||
# Disable gzip to protect against BREACH
|
||||
# Read https://trac.nginx.org/nginx/ticket/1720 (text/html cannot be disabled!)
|
||||
|
|
14
data/templates/yunohost/proc-hidepid.service
Normal file
14
data/templates/yunohost/proc-hidepid.service
Normal file
|
@ -0,0 +1,14 @@
|
|||
[Unit]
|
||||
Description=Mounts /proc with hidepid=2
|
||||
DefaultDependencies=no
|
||||
Before=sysinit.target
|
||||
Requires=local-fs.target
|
||||
After=local-fs.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/bin/mount -o remount,nosuid,nodev,noexec,hidepid=2 /proc
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=sysinit.target
|
|
@ -1,4 +1,3 @@
|
|||
avahi-daemon: {}
|
||||
dnsmasq:
|
||||
test_conf: dnsmasq --test
|
||||
dovecot:
|
||||
|
@ -52,6 +51,8 @@ yunohost-firewall:
|
|||
need_lock: true
|
||||
test_status: iptables -S | grep "^-A INPUT" | grep " --dport" | grep -q ACCEPT
|
||||
category: security
|
||||
yunomdns:
|
||||
category: mdns
|
||||
glances: null
|
||||
nsswitch: null
|
||||
ssl: null
|
||||
|
@ -68,3 +69,4 @@ rmilter: null
|
|||
php5-fpm: null
|
||||
php7.0-fpm: null
|
||||
nslcd: null
|
||||
avahi-daemon: null
|
||||
|
|
21
debian/changelog
vendored
21
debian/changelog
vendored
|
@ -1,3 +1,24 @@
|
|||
yunohost (4.2.8.1) stable; urgency=low
|
||||
|
||||
- [fix] Safer location for slapd backup during hdb/mdb migration (3c646b3d)
|
||||
|
||||
Thanks to all contributors <3 ! (ljf)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Fri, 27 Aug 2021 01:32:16 +0200
|
||||
|
||||
yunohost (4.2.8) stable; urgency=low
|
||||
|
||||
- [fix] ynh_permission_has_user not behaving properly when checking if a group is allowed (f0590907)
|
||||
- [enh] use yaml safeloader everywhere ([#1287](https://github.com/YunoHost/yunohost/pull/1287))
|
||||
- [enh] Add --no-safety-backup option to "yunohost app upgrade" ([#1286](https://github.com/YunoHost/yunohost/pull/1286))
|
||||
- [enh] Add --purge option to "yunohost app remove" ([#1285](https://github.com/YunoHost/yunohost/pull/1285))
|
||||
- [enh] Multimedia helper: check that home folder exists ([#1255](https://github.com/YunoHost/yunohost/pull/1255))
|
||||
- [i18n] Translations updated for French, Galician, German, Portuguese
|
||||
|
||||
Thanks to all contributors <3 ! (José M, Kay0u, Krakinou, ljf, Luca, mifegui, ppr, sagessylu)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 19 Aug 2021 19:11:19 +0200
|
||||
|
||||
yunohost (4.2.7) stable; urgency=low
|
||||
|
||||
Notable changes:
|
||||
|
|
5
debian/control
vendored
5
debian/control
vendored
|
@ -13,14 +13,15 @@ Depends: ${python3:Depends}, ${misc:Depends}
|
|||
, moulinette (>= 4.2), ssowat (>= 4.0)
|
||||
, python3-psutil, python3-requests, python3-dnspython, python3-openssl
|
||||
, python3-miniupnpc, python3-dbus, python3-jinja2
|
||||
, python3-toml, python3-packaging, python3-publicsuffix
|
||||
, python3-toml, python3-packaging, python3-publicsuffix,
|
||||
, python3-ldap, python3-zeroconf,
|
||||
, apt, apt-transport-https, apt-utils, dirmngr
|
||||
, php7.3-common, php7.3-fpm, php7.3-ldap, php7.3-intl
|
||||
, mariadb-server, php7.3-mysql
|
||||
, openssh-server, iptables, fail2ban, dnsutils, bind9utils
|
||||
, openssl, ca-certificates, netcat-openbsd, iproute2
|
||||
, slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd
|
||||
, dnsmasq, avahi-daemon, libnss-mdns, resolvconf, libnss-myhostname
|
||||
, dnsmasq, resolvconf, libnss-myhostname
|
||||
, postfix, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre
|
||||
, dovecot-core, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved, dovecot-antispam
|
||||
, rspamd, opendkim-tools, postsrsd, procmail, mailutils
|
||||
|
|
1
debian/postinst
vendored
1
debian/postinst
vendored
|
@ -18,6 +18,7 @@ do_configure() {
|
|||
bash /usr/share/yunohost/hooks/conf_regen/46-nsswitch init
|
||||
bash /usr/share/yunohost/hooks/conf_regen/06-slapd init
|
||||
bash /usr/share/yunohost/hooks/conf_regen/15-nginx init
|
||||
bash /usr/share/yunohost/hooks/conf_regen/37-mdns init
|
||||
fi
|
||||
else
|
||||
echo "Regenerating configuration, this might take a while..."
|
||||
|
|
|
@ -26,7 +26,7 @@ ACTIONSMAP_FILE = os.path.join(THIS_SCRIPT_DIR, "../data/actionsmap/yunohost.yml
|
|||
|
||||
|
||||
def ordered_yaml_load(stream):
|
||||
class OrderedLoader(yaml.Loader):
|
||||
class OrderedLoader(yaml.SafeLoader):
|
||||
pass
|
||||
|
||||
OrderedLoader.add_constructor(
|
||||
|
|
|
@ -83,7 +83,6 @@
|
|||
"yunohost_installing": "عملية تنصيب يونوهوست جارية …",
|
||||
"yunohost_not_installed": "إنَّ واي يونوهوست ليس مُنَصَّب أو هو مثبت حاليا بشكل خاطئ. قم بتنفيذ الأمر 'yunohost tools postinstall'",
|
||||
"migrations_list_conflict_pending_done": "لا يمكنك استخدام --previous و --done معًا على نفس سطر الأوامر.",
|
||||
"service_description_avahi-daemon": "يسمح لك بالنفاذ إلى خادومك عبر الشبكة المحلية باستخدام yunohost.local",
|
||||
"service_description_metronome": "يُدير حسابات الدردشة الفورية XMPP",
|
||||
"service_description_nginx": "يقوم بتوفير النفاذ و السماح بالوصول إلى كافة مواقع الويب المستضافة على خادومك",
|
||||
"service_description_postfix": "يقوم بإرسال و تلقي الرسائل البريدية الإلكترونية",
|
||||
|
|
|
@ -283,7 +283,6 @@
|
|||
"service_already_started": "El servei «{service}» ja està funcionant",
|
||||
"service_already_stopped": "Ja s'ha aturat el servei «{service}»",
|
||||
"service_cmd_exec_failed": "No s'ha pogut executar l'ordre «{command}»",
|
||||
"service_description_avahi-daemon": "Permet accedir al servidor via «yunohost.local» en la xarxa local",
|
||||
"service_description_dnsmasq": "Gestiona la resolució del nom de domini (DNS)",
|
||||
"service_description_dovecot": "Permet als clients de correu accedir/recuperar correus (via IMAP i POP3)",
|
||||
"service_description_fail2ban": "Protegeix contra els atacs de força bruta i a altres atacs provinents d'Internet",
|
||||
|
|
|
@ -124,7 +124,7 @@
|
|||
"upnp_dev_not_found": "Es konnten keine UPnP Geräte gefunden werden",
|
||||
"upnp_disabled": "UPnP deaktiviert",
|
||||
"upnp_enabled": "UPnP aktiviert",
|
||||
"upnp_port_open_failed": "UPnP Port konnte nicht geöffnet werden.",
|
||||
"upnp_port_open_failed": "Port konnte nicht via UPnP geöffnet werden",
|
||||
"user_created": "Benutzer erstellt",
|
||||
"user_creation_failed": "Benutzer konnte nicht erstellt werden {user}: {error}",
|
||||
"user_deleted": "Benutzer gelöscht",
|
||||
|
@ -528,7 +528,7 @@
|
|||
"migrations_no_such_migration": "Es existiert keine Migration genannt '{id}'",
|
||||
"migrations_running_forward": "Durchführen der Migrationen {id}...",
|
||||
"migrations_skip_migration": "Überspringe Migrationen {id}...",
|
||||
"password_too_simple_2": "Dieses Passwort gehört zu den meistverwendeten der Welt. Bitte nehmen Sie etwas einzigartigeres",
|
||||
"password_too_simple_2": "Das Passwort muss mindestens 8 Zeichen lang sein und Gross- sowie Kleinbuchstaben enthalten",
|
||||
"password_listed": "Dieses Passwort zählt zu den meistgenutzten Passwörtern der Welt. Bitte wähle ein anderes, einzigartigeres Passwort.",
|
||||
"operation_interrupted": "Wurde die Operation manuell unterbrochen?",
|
||||
"invalid_number": "Muss eine Zahl sein",
|
||||
|
@ -539,8 +539,8 @@
|
|||
"permission_already_allowed": "Die Gruppe '{group}' hat die Berechtigung '{permission}' bereits erhalten",
|
||||
"pattern_password_app": "Entschuldigen Sie bitte! Passwörter dürfen folgende Zeichen nicht enthalten: {forbidden_chars}",
|
||||
"pattern_email_forward": "Es muss sich um eine gültige E-Mail-Adresse handeln. Das Symbol '+' wird akzeptiert (zum Beispiel : maxmuster@beispiel.com oder maxmuster+yunohost@beispiel.com)",
|
||||
"password_too_simple_4": "Dass Passwort muss mindestens 12 Zeichen lang sein und Zahlen, Klein- und Grossbuchstaben und Sonderzeichen enthalten",
|
||||
"password_too_simple_3": "Das Passwort muss mindestens 8 Zeichen lang sein und Zahlen, Klein- und Grossbuchstaben und Sonderzeichen enthalten",
|
||||
"password_too_simple_4": "Das Passwort muss mindestens 12 Zeichen lang sein und Grossbuchstaben, Kleinbuchstaben, Zahlen und Sonderzeichen enthalten",
|
||||
"password_too_simple_3": "Das Passwort muss mindestens 8 Zeichen lang sein und Grossbuchstaben, Kleinbuchstaben, Zahlen und Sonderzeichen enthalten",
|
||||
"regenconf_file_manually_removed": "Die Konfigurationsdatei '{conf}' wurde manuell gelöscht und wird nicht erstellt",
|
||||
"regenconf_file_manually_modified": "Die Konfigurationsdatei '{conf}' wurde manuell bearbeitet und wird nicht aktualisiert",
|
||||
"regenconf_file_kept_back": "Die Konfigurationsdatei '{conf}' sollte von \"regen-conf\" (Kategorie {category}) gelöscht werden, wurde aber beibehalten.",
|
||||
|
@ -597,7 +597,6 @@
|
|||
"service_description_fail2ban": "Schützt gegen Brute-Force-Angriffe und andere Angriffe aus dem Internet",
|
||||
"service_description_dovecot": "Ermöglicht es E-Mail-Clients auf Konten zuzugreifen (IMAP und POP3)",
|
||||
"service_description_dnsmasq": "Verarbeitet die Auflösung des Domainnamens (DNS)",
|
||||
"service_description_avahi-daemon": "Erlaubt, den Server im lokalen Netz über 'yunohost.local' zu erreichen",
|
||||
"restore_backup_too_old": "Dieses Backup kann nicht wieder hergestellt werden, weil es von einer zu alten YunoHost Version stammt.",
|
||||
"service_description_slapd": "Speichert Benutzer, Domains und verbundene Informationen",
|
||||
"service_description_rspamd": "Spamfilter und andere E-Mail-Merkmale",
|
||||
|
@ -631,5 +630,9 @@
|
|||
"unknown_main_domain_path": "Unbekannte:r Domain oder Pfad für '{app}'. Du musst eine Domain und einen Pfad setzen, um die URL für Berechtigungen zu setzen.",
|
||||
"yunohost_postinstall_end_tip": "Post-install ist fertig! Um das Setup abzuschliessen, wird empfohlen:\n - einen ersten Benutzer über den Bereich 'Benutzer*in' im Adminbereich hinzuzufügen (oder mit 'yunohost user create <username>' in der Kommandezeile);\n - mögliche Fehler zu diagnostizieren über den Bereich 'Diagnose' im Adminbereich (oder mit 'yunohost diagnosis run' in der Kommandozeile;\n - Die Abschnitte 'Install YunoHost' und 'Geführte Tour' im Administratorenhandbuch zu lesen: https://yunohost.org/admindoc.",
|
||||
"user_already_exists": "Der Benutzer '{user}' ist bereits vorhanden",
|
||||
"update_apt_cache_warning": "Beim Versuch den Cache für APT (Debians Paketmanager) zu aktualisieren, ist etwas schief gelaufen. Hier ist ein Dump der Zeilen aus sources.list, die Ihnen vielleicht dabei helfen, das Problem zu identifizieren:\n{sourceslist}"
|
||||
"update_apt_cache_warning": "Beim Versuch den Cache für APT (Debians Paketmanager) zu aktualisieren, ist etwas schief gelaufen. Hier ist ein Dump der Zeilen aus sources.list, die Ihnen vielleicht dabei helfen, das Problem zu identifizieren:\n{sourceslist}",
|
||||
"global_settings_setting_security_webadmin_allowlist": "IP-Adressen, die auf die Verwaltungsseite zugreifen dürfen. Kommasepariert.",
|
||||
"global_settings_setting_security_webadmin_allowlist_enabled": "Erlaube nur bestimmten IP-Adressen den Zugriff auf die Verwaltungsseite.",
|
||||
"disk_space_not_sufficient_update": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu aktuallisieren",
|
||||
"disk_space_not_sufficient_install": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu installieren"
|
||||
}
|
|
@ -32,7 +32,7 @@
|
|||
"app_location_unavailable": "This URL is either unavailable, or conflicts with the already installed app(s):\n{apps}",
|
||||
"app_manifest_invalid": "Something is wrong with the app manifest: {error}",
|
||||
"app_manifest_install_ask_domain": "Choose the domain where this app should be installed",
|
||||
"app_manifest_install_ask_path": "Choose the path where this app should be installed",
|
||||
"app_manifest_install_ask_path": "Choose the URL path (after the domain) where this app should be installed",
|
||||
"app_manifest_install_ask_password": "Choose an administration password for this app",
|
||||
"app_manifest_install_ask_admin": "Choose an administrator user for this app",
|
||||
"app_manifest_install_ask_is_public": "Should this app be exposed to anonymous visitors?",
|
||||
|
@ -184,6 +184,7 @@
|
|||
"diagnosis_dns_discrepancy": "The following DNS record does not seem to follow the recommended configuration:<br>Type: <code>{type}</code><br>Name: <code>{name}</code><br>Current value: <code>{current}</code><br>Expected value: <code>{value}</code>",
|
||||
"diagnosis_dns_point_to_doc": "Please check the documentation at <a href='https://yunohost.org/dns_config'>https://yunohost.org/dns_config</a> if you need help about configuring DNS records.",
|
||||
"diagnosis_dns_try_dyndns_update_force": "This domain's DNS configuration should automatically be managed by YunoHost. If that's not the case, you can try to force an update using <cmd>yunohost dyndns update --force</cmd>.",
|
||||
"diagnosis_dns_specialusedomain": "Domain {domain} is based on a special-use top-level domain (TLD) and is therefore not expected to have actual DNS records.",
|
||||
"diagnosis_domain_expiration_not_found": "Unable to check the expiration date for some domains",
|
||||
"diagnosis_domain_not_found_details": "The domain {domain} doesn't exist in WHOIS database or is expired!",
|
||||
"diagnosis_domain_expiration_not_found_details": "The WHOIS information for domain {domain} doesn't seem to contain the information about the expiration date?",
|
||||
|
@ -249,6 +250,14 @@
|
|||
"diagnosis_description_web": "Web",
|
||||
"diagnosis_description_mail": "Email",
|
||||
"diagnosis_description_regenconf": "System configurations",
|
||||
"diagnosis_description_apps": "Applications",
|
||||
"diagnosis_apps_allgood": "All installed apps respect basic packaging practices",
|
||||
"diagnosis_apps_issue": "An issue was found for app {app}",
|
||||
"diagnosis_apps_not_in_app_catalog": "This application is not in YunoHost's application catalog. If it was in the past and got removed, you should consider uninstalling this app as it won't receive upgrade, and may compromise the integrity and security of your system.",
|
||||
"diagnosis_apps_broken": "This application is currently flagged as broken on YunoHost's application catalog. This may be a temporary issue while the maintainers attempt to fix the issue. In the meantime, upgrading this app is disabled.",
|
||||
"diagnosis_apps_bad_quality": "This application is currently flagged as broken on YunoHost's application catalog. This may be a temporary issue while the maintainers attempt to fix the issue. In the meantime, upgrading this app is disabled.",
|
||||
"diagnosis_apps_outdated_ynh_requirement": "This app's installed version only requires yunohost >= 2.x, which tends to indicate that it's not up to date with recommended packaging practices and helpers. You should really consider upgrading it.",
|
||||
"diagnosis_apps_deprecated_practices": "This app's installed version still uses some super-old deprecated packaging practices. You should really consider upgrading it.",
|
||||
"diagnosis_ports_could_not_diagnose": "Could not diagnose if ports are reachable from outside in IPv{ipversion}.",
|
||||
"diagnosis_ports_could_not_diagnose_details": "Error: {error}",
|
||||
"diagnosis_ports_unreachable": "Port {port} is not reachable from outside.",
|
||||
|
@ -260,6 +269,7 @@
|
|||
"diagnosis_http_hairpinning_issue_details": "This is probably because of your ISP box / router. As a result, people from outside your local network will be able to access your server as expected, but not people from inside the local network (like you, probably?) when using the domain name or global IP. You may be able to improve the situation by having a look at <a href='https://yunohost.org/dns_local_network'>https://yunohost.org/dns_local_network</a>",
|
||||
"diagnosis_http_could_not_diagnose": "Could not diagnose if domains are reachable from outside in IPv{ipversion}.",
|
||||
"diagnosis_http_could_not_diagnose_details": "Error: {error}",
|
||||
"diagnosis_http_localdomain": "Domain {domain}, with a .local TLD, is not expected to be reached from outside the local network.",
|
||||
"diagnosis_http_ok": "Domain {domain} is reachable through HTTP from outside the local network.",
|
||||
"diagnosis_http_timeout": "Timed-out while trying to contact your server from outside. It appears to be unreachable.<br>1. The most common cause for this issue is that port 80 (and 443) <a href='https://yunohost.org/isp_box_config'>are not correctly forwarded to your server</a>.<br>2. You should also make sure that the service nginx is running<br>3. On more complex setups: make sure that no firewall or reverse-proxy is interfering.",
|
||||
"diagnosis_http_connection_error": "Connection error: could not connect to the requested domain, it's very likely unreachable.",
|
||||
|
@ -341,6 +351,7 @@
|
|||
"global_settings_setting_smtp_relay_password": "SMTP relay host password",
|
||||
"global_settings_setting_security_webadmin_allowlist_enabled": "Allow only some IPs to access the webadmin.",
|
||||
"global_settings_setting_security_webadmin_allowlist": "IP adresses allowed to access the webadmin. Comma-separated.",
|
||||
"global_settings_setting_security_experimental_enabled": "Enable experimental security features (don't enable this if you don't know what you're doing!)",
|
||||
"global_settings_setting_backup_compress_tar_archives": "When creating new backups, compress the archives (.tar.gz) instead of uncompressed archives (.tar). N.B. : enabling this option means create lighter backup archives, but the initial backup procedure will be significantly longer and heavy on CPU.",
|
||||
"global_settings_unknown_type": "Unexpected situation, the setting {setting} appears to have the type {unknown_type} but it is not a type supported by the system.",
|
||||
"good_practices_about_admin_password": "You are now about to define a new administration password. The password should be at least 8 characters long—though it is good practice to use a longer password (i.e. a passphrase) and/or to use a variation of characters (uppercase, lowercase, digits and special characters).",
|
||||
|
@ -370,6 +381,8 @@
|
|||
"invalid_regex": "Invalid regex:'{regex}'",
|
||||
"ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it",
|
||||
"iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it",
|
||||
"ldap_server_down": "Unable to reach LDAP server",
|
||||
"ldap_server_is_down_restart_it": "The LDAP service is down, attempt to restart it...",
|
||||
"log_corrupted_md_file": "The YAML metadata file associated with logs is damaged: '{md_file}\nError: {error}'",
|
||||
"log_link_to_log": "Full log of this operation: '<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>'",
|
||||
"log_help_to_get_log": "To view the log of the operation '{desc}', use the command 'yunohost log show {name}{name}'",
|
||||
|
@ -399,7 +412,7 @@
|
|||
"log_letsencrypt_cert_install": "Install a Let's Encrypt certificate on '{}' domain",
|
||||
"log_permission_create": "Create permission '{}'",
|
||||
"log_permission_delete": "Delete permission '{}'",
|
||||
"log_permission_url": "Update url related to permission '{}'",
|
||||
"log_permission_url": "Update URL related to permission '{}'",
|
||||
"log_selfsigned_cert_install": "Install self-signed certificate on '{}' domain",
|
||||
"log_letsencrypt_cert_renew": "Renew '{}' Let's Encrypt certificate",
|
||||
"log_regen_conf": "Regenerate system configurations '{}'",
|
||||
|
@ -478,6 +491,7 @@
|
|||
"migrations_to_be_ran_manually": "Migration {id} has to be run manually. Please go to Tools → Migrations on the webadmin page, or run `yunohost tools migrations run`.",
|
||||
"not_enough_disk_space": "Not enough free space on '{path}'",
|
||||
"invalid_number": "Must be a number",
|
||||
"invalid_password": "Invalid password",
|
||||
"operation_interrupted": "The operation was manually interrupted?",
|
||||
"packages_upgrade_failed": "Could not upgrade all the packages",
|
||||
"password_listed": "This password is among the most used passwords in the world. Please choose something more unique.",
|
||||
|
@ -561,7 +575,7 @@
|
|||
"service_already_started": "The service '{service}' is running already",
|
||||
"service_already_stopped": "The service '{service}' has already been stopped",
|
||||
"service_cmd_exec_failed": "Could not execute the command '{command}'",
|
||||
"service_description_avahi-daemon": "Allows you to reach your server using 'yunohost.local' in your local network",
|
||||
"service_description_yunomdns": "Allows you to reach your server using 'yunohost.local' in your local network",
|
||||
"service_description_dnsmasq": "Handles domain name resolution (DNS)",
|
||||
"service_description_dovecot": "Allows e-mail clients to access/fetch email (via IMAP and POP3)",
|
||||
"service_description_fail2ban": "Protects against brute-force and other kinds of attacks from the Internet",
|
||||
|
|
|
@ -332,7 +332,6 @@
|
|||
"hook_exec_failed": "Ne povis funkcii skripto: {path}",
|
||||
"global_settings_cant_open_settings": "Ne eblis malfermi agordojn, tial: {reason}",
|
||||
"user_created": "Uzanto kreita",
|
||||
"service_description_avahi-daemon": "Permesas al vi atingi vian servilon uzante 'yunohost.local' en via loka reto",
|
||||
"certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domain}! (Uzu --forte pretervidi)",
|
||||
"regenconf_updated": "Agordo ĝisdatigita por '{category}'",
|
||||
"update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourceslist}",
|
||||
|
|
|
@ -238,7 +238,6 @@
|
|||
"service_description_fail2ban": "Protege contra ataques de fuerza bruta y otras clases de ataques desde Internet",
|
||||
"service_description_dovecot": "Permite a los clientes de correo acceder/obtener correo (vía IMAP y POP3)",
|
||||
"service_description_dnsmasq": "Maneja la resolución de nombres de dominio (DNS)",
|
||||
"service_description_avahi-daemon": "Permite acceder a su servidor usando «yunohost.local» en su red local",
|
||||
"server_reboot_confirm": "El servidor se reiniciará inmediatamente ¿está seguro? [{answers}]",
|
||||
"server_reboot": "El servidor se reiniciará",
|
||||
"server_shutdown_confirm": "El servidor se apagará inmediatamente ¿está seguro? [{answers}]",
|
||||
|
|
|
@ -156,7 +156,7 @@
|
|||
"certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain} n’est pas émis par Let’s Encrypt. Impossible de le renouveler automatiquement !",
|
||||
"certmanager_attempt_to_renew_valid_cert": "Le certificat pour le domaine {domain} n’est pas sur le point d’expirer ! (Vous pouvez utiliser --force si vous savez ce que vous faites)",
|
||||
"certmanager_domain_http_not_working": "Le domaine {domain} ne semble pas être accessible via HTTP. Merci de vérifier la catégorie 'Web' dans le diagnostic pour plus d'informations. (Ou si vous savez ce que vous faites, utilisez '--no-checks' pour désactiver la vérification.)",
|
||||
"certmanager_domain_dns_ip_differs_from_public_ip": "L'enregistrement DNS du domaine '{domain}' est différent de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. Si vous avez récemment modifié votre enregistrement A, veuillez attendre sa propagation (des vérificateurs de propagation DNS sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez '--no-checks' pour désactiver ces contrôles)",
|
||||
"certmanager_domain_dns_ip_differs_from_public_ip": "Les enregistrements DNS du domaine '{domain}' sont différents de l’adresse IP de ce serveur. Pour plus d'informations, veuillez consulter la catégorie \"Enregistrements DNS\" dans la section diagnostic. Si vous avez récemment modifié votre enregistrement A, veuillez attendre sa propagation (des vérificateurs de propagation DNS sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez '--no-checks' pour désactiver ces contrôles)",
|
||||
"certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain} (fichier : {file}), la cause est : {reason}",
|
||||
"certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain} »",
|
||||
"certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain} »",
|
||||
|
@ -217,10 +217,10 @@
|
|||
"backup_couldnt_bind": "Impossible de lier {src} avec {dest}.",
|
||||
"domain_dns_conf_is_just_a_recommendation": "Cette commande vous montre la configuration *recommandée*. Elle ne configure pas le DNS pour vous. Il est de votre ressort de configurer votre zone DNS chez votre registrar/fournisseur conformément à cette recommandation.",
|
||||
"migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migration via le chemin '%s'",
|
||||
"migrations_loading_migration": "Chargement de la migration {id}...",
|
||||
"migrations_loading_migration": "Chargement de la migration {id} ...",
|
||||
"migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation",
|
||||
"migrations_no_migrations_to_run": "Aucune migration à lancer",
|
||||
"migrations_skip_migration": "Ignorer et passer la migration {id}...",
|
||||
"migrations_skip_migration": "Ignorer et passer la migration {id} ...",
|
||||
"server_shutdown": "Le serveur va s’éteindre",
|
||||
"server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers}]",
|
||||
"server_reboot": "Le serveur va redémarrer",
|
||||
|
@ -234,7 +234,7 @@
|
|||
"migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.",
|
||||
"migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations run`.",
|
||||
"migrations_need_to_accept_disclaimer": "Pour lancer la migration {id}, vous devez accepter cet avertissement :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.",
|
||||
"service_description_avahi-daemon": "Vous permet d’atteindre votre serveur en utilisant « yunohost.local » sur votre réseau local",
|
||||
"service_description_yunomdns": "Vous permet d’atteindre votre serveur en utilisant « yunohost.local » sur votre réseau local",
|
||||
"service_description_dnsmasq": "Gère la résolution des noms de domaine (DNS)",
|
||||
"service_description_dovecot": "Permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)",
|
||||
"service_description_fail2ban": "Protège contre les attaques brute-force et autres types d’attaques venant d’Internet",
|
||||
|
@ -285,7 +285,7 @@
|
|||
"mail_unavailable": "Cette adresse de courriel est réservée et doit être automatiquement attribuée au tout premier utilisateur",
|
||||
"good_practices_about_admin_password": "Vous êtes sur le point de définir un nouveau mot de passe d'administration. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou d'utiliser une combinaison de caractères (majuscules, minuscules, chiffres et caractères spéciaux).",
|
||||
"good_practices_about_user_password": "Vous êtes sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères, bien qu'il soit recommandé d'utiliser un mot de passe plus long (c'est-à-dire une phrase secrète) et/ou une combinaison de caractères (majuscules, minuscules, chiffres et caractères spéciaux).",
|
||||
"password_listed": "Ce mot de passe fait partie des mots de passe les plus utilisés dans le monde. Veuillez en choisir un autre moins commun et plus fort.",
|
||||
"password_listed": "Ce mot de passe fait partie des mots de passe les plus utilisés dans le monde. Veuillez en choisir un autre moins commun et plus robuste.",
|
||||
"password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères",
|
||||
"password_too_simple_2": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules et des minuscules",
|
||||
"password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux",
|
||||
|
@ -342,7 +342,7 @@
|
|||
"regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'",
|
||||
"regenconf_dry_pending_applying": "Vérification de la configuration en attente qui aurait été appliquée pour la catégorie '{category}'…",
|
||||
"regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'",
|
||||
"regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}'...",
|
||||
"regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}' ...",
|
||||
"service_regen_conf_is_deprecated": "'yunohost service regen-conf' est obsolète ! Veuillez plutôt utiliser 'yunohost tools regen-conf' à la place.",
|
||||
"tools_upgrade_at_least_one": "Veuillez spécifier '--apps' ou '--system'",
|
||||
"tools_upgrade_cant_both": "Impossible de mettre à niveau le système et les applications en même temps",
|
||||
|
@ -381,7 +381,7 @@
|
|||
"migrations_already_ran": "Ces migrations sont déjà effectuées : {ids}",
|
||||
"migrations_dependencies_not_satisfied": "Exécutez ces migrations : '{dependencies_id}', avant migration {id}.",
|
||||
"migrations_failed_to_load_migration": "Impossible de charger la migration {id} : {error}",
|
||||
"migrations_running_forward": "Exécution de la migration {id}...",
|
||||
"migrations_running_forward": "Exécution de la migration {id} ...",
|
||||
"migrations_success_forward": "Migration {id} terminée",
|
||||
"operation_interrupted": "L'opération a-t-elle été interrompue manuellement ?",
|
||||
"permission_already_exist": "L’autorisation '{permission}' existe déjà",
|
||||
|
@ -554,24 +554,24 @@
|
|||
"diagnosis_swap_tip": "Merci d'être prudent et conscient que si vous hébergez une partition SWAP sur une carte SD ou un disque SSD, cela risque de réduire drastiquement l’espérance de vie du périphérique.",
|
||||
"restore_already_installed_apps": "Les applications suivantes ne peuvent pas être restaurées car elles sont déjà installées : {apps}",
|
||||
"regenconf_need_to_explicitly_specify_ssh": "La configuration de ssh a été modifiée manuellement. Vous devez explicitement indiquer la mention --force à \"ssh\" pour appliquer les changements.",
|
||||
"migration_0015_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires...",
|
||||
"migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent être mis à jour séparément...",
|
||||
"migration_0015_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires ...",
|
||||
"migration_0015_specific_upgrade": "Démarrage de la mise à jour des paquets du système qui doivent être mis à jour séparément ...",
|
||||
"migration_0015_modified_files": "Veuillez noter que les fichiers suivants ont été modifiés manuellement et pourraient être écrasés à la suite de la mise à niveau : {manually_modified_files}",
|
||||
"migration_0015_problematic_apps_warning": "Veuillez noter que des applications qui peuvent poser problèmes ont été détectées. Il semble qu'elles n'aient pas été installées à partir du catalogue d'applications YunoHost, ou bien qu'elles ne soient pas signalées comme \"fonctionnelles\". Par conséquent, il n'est pas possible de garantir que les applications suivantes fonctionneront encore après la mise à niveau : {problematic_apps}",
|
||||
"migration_0015_general_warning": "Veuillez noter que cette migration est une opération délicate. L'équipe YunoHost a fait de son mieux pour la revérifier et la tester, mais la migration pourrait quand même casser des éléments du système ou de ses applications.\n\nIl est donc recommandé :\n…- de faire une sauvegarde de toute donnée ou application critique. Plus d'informations ici https://yunohost.org/backup ;\n…- d'être patient après le lancement de la migration. Selon votre connexion internet et votre matériel, la mise à niveau peut prendre jusqu'à quelques heures.",
|
||||
"migration_0015_system_not_fully_up_to_date": "Votre système n'est pas entièrement à jour. Veuillez effectuer une mise à jour normale avant de lancer la migration vers Buster.",
|
||||
"migration_0015_not_enough_free_space": "L'espace libre est très faible dans /var/ ! Vous devriez avoir au moins 1 Go de libre pour effectuer cette migration.",
|
||||
"migration_0015_not_stretch": "La distribution Debian actuelle n'est pas Stretch !",
|
||||
"migration_0015_yunohost_upgrade": "Démarrage de la mise à jour de YunoHost...",
|
||||
"migration_0015_yunohost_upgrade": "Démarrage de la mise à jour de YunoHost ...",
|
||||
"migration_0015_still_on_stretch_after_main_upgrade": "Quelque chose s'est mal passé lors de la mise à niveau, le système semble toujours être sous Debian Stretch",
|
||||
"migration_0015_main_upgrade": "Démarrage de la mise à niveau générale...",
|
||||
"migration_0015_patching_sources_list": "Mise à jour du fichier sources.lists...",
|
||||
"migration_0015_main_upgrade": "Démarrage de la mise à niveau générale ...",
|
||||
"migration_0015_patching_sources_list": "Mise à jour du fichier sources.lists ...",
|
||||
"migration_0015_start": "Démarrage de la migration vers Buster",
|
||||
"migration_description_0015_migrate_to_buster": "Mise à niveau du système vers Debian Buster et YunoHost 4.x",
|
||||
"diagnosis_dns_try_dyndns_update_force": "La configuration DNS de ce domaine devrait être automatiquement gérée par YunoHost. Si ce n'est pas le cas, vous pouvez essayer de forcer une mise à jour en utilisant <cmd>yunohost dyndns update --force</cmd>.",
|
||||
"app_packaging_format_not_supported": "Cette application ne peut pas être installée car son format n'est pas pris en charge par votre version de YunoHost. Vous devriez probablement envisager de mettre à jour votre système.",
|
||||
"migration_0015_weak_certs": "Il a été constaté que les certificats suivants utilisent encore des algorithmes de signature peu robustes et doivent être mis à jour pour être compatibles avec la prochaine version de NGINX : {certs}",
|
||||
"global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer des archives non-compressées lors de la création des backups. N.B. Activer cette option permet de créer des archives plus légères, mais aussi d'avoir des procédures de backup significativement plus longues et plus gourmandes en CPU.",
|
||||
"global_settings_setting_backup_compress_tar_archives": "Compresser les archives (.tar.gz) au lieu de créer des archives non-compressées lors de la création des sauvegardes. N.B. Activer cette option permet de créer des archives plus légères, mais aussi d'avoir des procédures de sauvegarde significativement plus longues et plus gourmandes en CPU.",
|
||||
"migration_description_0018_xtable_to_nftable": "Migrer les anciennes règles de trafic réseau vers le nouveau système basé sur nftables",
|
||||
"service_description_php7.3-fpm": "Exécute les applications écrites en PHP avec NGINX",
|
||||
"migration_0018_failed_to_reset_legacy_rules": "La réinitialisation des règles iptable par défaut a échoué : {error}",
|
||||
|
@ -612,7 +612,7 @@
|
|||
"additional_urls_already_removed": "URL supplémentaire '{url}' déjà supprimées pour la permission '{permission}'",
|
||||
"invalid_number": "Doit être un nombre",
|
||||
"migration_description_0019_extend_permissions_features": "Étendre et retravailler le système de gestion des permissions applicatives",
|
||||
"diagnosis_basesystem_hardware_model": "Le modèle du serveur est {model}",
|
||||
"diagnosis_basesystem_hardware_model": "Le modèle/architecture du serveur est {model}",
|
||||
"diagnosis_backports_in_sources_list": "Il semble qu'apt (le gestionnaire de paquets) soit configuré pour utiliser le dépôt des rétroportages (backports). A moins que vous ne sachiez vraiment ce que vous faites, nous vous déconseillons fortement d'installer des paquets provenant des rétroportages, car cela risque de créer des instabilités ou des conflits sur votre système.",
|
||||
"postinstall_low_rootfsspace": "Le système de fichiers a une taille totale inférieure à 10 Go, ce qui est préoccupant et devrait attirer votre attention ! Vous allez certainement arriver à court d'espace disque (très) rapidement ! Il est recommandé d'avoir au moins 16 Go à la racine pour ce système de fichiers. Si vous voulez installer YunoHost malgré cet avertissement, relancez la post-installation avec --force-diskspace",
|
||||
"domain_remove_confirm_apps_removal": "Le retrait de ce domaine retirera aussi ces applications :\n{apps}\n\nÊtes vous sûr de vouloir cela ? [{answers}]",
|
||||
|
@ -633,5 +633,9 @@
|
|||
"diagnosis_sshd_config_inconsistent_details": "Veuillez exécuter <cmd>yunohost settings set security.ssh.port -v VOTRE_PORT_SSH</cmd> pour définir le port SSH, et vérifiez <cmd>yunohost tools regen-conf ssh --dry-run --with-diff</cmd> et <cmd>yunohost tools regen-conf ssh --force</cmd> pour réinitialiser votre configuration aux recommandations YunoHost.",
|
||||
"diagnosis_sshd_config_inconsistent": "Il semble que le port SSH a été modifié manuellement dans /etc/ssh/sshd_config. Depuis YunoHost 4.2, un nouveau paramètre global 'security.ssh.port' est disponible pour éviter de modifier manuellement la configuration.",
|
||||
"diagnosis_sshd_config_insecure": "La configuration SSH semble avoir été modifiée manuellement et n'est pas sécurisée car elle ne contient aucune directive 'AllowGroups' ou 'AllowUsers' pour limiter l'accès aux utilisateurs autorisés.",
|
||||
"backup_create_size_estimation": "L'archive contiendra environ {size} de données."
|
||||
"backup_create_size_estimation": "L'archive contiendra environ {size} de données.",
|
||||
"global_settings_setting_security_webadmin_allowlist": "Adresses IP autorisées à accéder à la page web du portail d'administration (webadmin). Elles doivent être séparées par une virgule.",
|
||||
"global_settings_setting_security_webadmin_allowlist_enabled": "Autorisez seulement certaines IP à accéder à la page web du portail d'administration (webadmin).",
|
||||
"diagnosis_http_localdomain": "Le domaine {domain}, avec un TLD .local, ne devrait pas être atteint depuis l'extérieur du réseau local.",
|
||||
"diagnosis_dns_specialusedomain": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial et ne devrait donc pas avoir d'enregistrements DNS réels."
|
||||
}
|
181
locales/gl.json
181
locales/gl.json
|
@ -67,7 +67,7 @@
|
|||
"app_remove_after_failed_install": "Eliminando a app debido ao fallo na instalación...",
|
||||
"app_requirements_unmeet": "Non se cumpren os requerimentos de {app}, o paquete {pkgname} ({version}) debe ser {spec}",
|
||||
"app_requirements_checking": "Comprobando os paquetes requeridos por {app}...",
|
||||
"app_removed": "{app} eliminada",
|
||||
"app_removed": "{app} desinstalada",
|
||||
"app_not_properly_removed": "{app} non se eliminou de xeito correcto",
|
||||
"app_not_installed": "Non se puido atopar {app} na lista de apps instaladas: {all_apps}",
|
||||
"app_not_correctly_installed": "{app} semella que non está instalada correctamente",
|
||||
|
@ -345,5 +345,182 @@
|
|||
"global_settings_setting_smtp_relay_password": "Contrasinal no repetidor SMTP",
|
||||
"global_settings_setting_smtp_relay_user": "Conta de usuaria no repetidor SMTP",
|
||||
"global_settings_setting_smtp_relay_port": "Porto do repetidor SMTP",
|
||||
"global_settings_setting_smtp_relay_host": "Servidor repetidor SMTP para enviar emails no lugar da túa instancia yunohost. É útil se estás nunha destas situacións: o teu porto 25 está bloqueado polo teu provedor ISP u VPN, se tes unha IP residencial nunha lista DUHL, se non podes configurar DNS inversa ou se este servidor non ten conexión directa a internet e queres utilizar outro para enviar os emails."
|
||||
"global_settings_setting_smtp_relay_host": "Servidor repetidor SMTP para enviar emails no lugar da túa instancia yunohost. É útil se estás nunha destas situacións: o teu porto 25 está bloqueado polo teu provedor ISP u VPN, se tes unha IP residencial nunha lista DUHL, se non podes configurar DNS inversa ou se este servidor non ten conexión directa a internet e queres utilizar outro para enviar os emails.",
|
||||
"group_updated": "Grupo '{group}' actualizado",
|
||||
"group_unknown": "Grupo descoñecido '{group}'",
|
||||
"group_deletion_failed": "Non se eliminou o grupo '{group}': {error}",
|
||||
"group_deleted": "Grupo '{group}' eliminado",
|
||||
"group_cannot_be_deleted": "O grupo {group} non se pode eliminar manualmente.",
|
||||
"group_cannot_edit_primary_group": "O grupo '{group}' non se pode editar manualmente. É o grupo primario que contén só a unha usuaria concreta.",
|
||||
"group_cannot_edit_visitors": "O grupo 'visitors' non se pode editar manualmente. É un grupo especial que representa a tódas visitantes anónimas",
|
||||
"group_cannot_edit_all_users": "O grupo 'all_users' non se pode editar manualmente. É un grupo especial que contén tódalas usuarias rexistradas en YunoHost",
|
||||
"global_settings_setting_security_webadmin_allowlist": "Enderezos IP con permiso para acceder á webadmin. Separados por vírgulas.",
|
||||
"global_settings_setting_security_webadmin_allowlist_enabled": "Permitir que só algúns IPs accedan á webadmin.",
|
||||
"disk_space_not_sufficient_update": "Non hai espazo suficiente no disco para actualizar esta aplicación",
|
||||
"disk_space_not_sufficient_install": "Non queda espazo suficiente no disco para instalar esta aplicación",
|
||||
"log_help_to_get_log": "Para ver o rexistro completo da operación '{desc}', usa o comando 'yunohost log show {name}{name}'",
|
||||
"log_link_to_log": "Rexistro completo desta operación: '<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>'",
|
||||
"log_corrupted_md_file": "O ficheiro YAML con metadatos asociado aos rexistros está danado: '{md_file}\nErro: {error}'",
|
||||
"iptables_unavailable": "Non podes andar remexendo en iptables aquí. Ou ben estás nun contedor ou o teu kernel non ten soporte para isto",
|
||||
"ip6tables_unavailable": "Non podes remexer en ip6tables aquí. Ou ben estás nun contedor ou o teu kernel non ten soporte para isto",
|
||||
"invalid_regex": "Regex non válido: '{regex}'",
|
||||
"installation_complete": "Instalación completa",
|
||||
"hook_name_unknown": "Nome descoñecido do gancho '{name}'",
|
||||
"hook_list_by_invalid": "Esta propiedade non se pode usar para enumerar os ganchos",
|
||||
"hook_json_return_error": "Non se puido ler a info de retorno do gancho {path}. Erro: {msg}. Contido en bruto: {raw_content}",
|
||||
"hook_exec_not_terminated": "O script non rematou correctamente: {path}",
|
||||
"hook_exec_failed": "Non se executou o script: {path}",
|
||||
"group_user_not_in_group": "A usuaria {user} non está no grupo {group}",
|
||||
"group_user_already_in_group": "A usuaria {user} xa está no grupo {group}",
|
||||
"group_update_failed": "Non se actualizou o grupo '{group}': {error}",
|
||||
"log_permission_delete": "Eliminar permiso '{}'",
|
||||
"log_permission_create": "Crear permiso '{}'",
|
||||
"log_letsencrypt_cert_install": "Instalar un certificado Let's Encrypt para o dominio '{}'",
|
||||
"log_dyndns_update": "Actualizar o IP asociado ao teu subdominio YunoHost '{}'",
|
||||
"log_dyndns_subscribe": "Subscribirse a un subdominio YunoHost '{}'",
|
||||
"log_domain_remove": "Eliminar o dominio '{}' da configuración do sistema",
|
||||
"log_domain_add": "Engadir dominio '{}' á configuración do sistema",
|
||||
"log_remove_on_failed_install": "Eliminar '{}' tras unha instalación fallida",
|
||||
"log_remove_on_failed_restore": "Eliminar '{}' tras un intento fallido de restablecemento desde copia",
|
||||
"log_backup_restore_app": "Restablecer '{}' desde unha copia de apoio",
|
||||
"log_backup_restore_system": "Restablecer o sistema desde unha copia de apoio",
|
||||
"log_backup_create": "Crear copia de apoio",
|
||||
"log_available_on_yunopaste": "Este rexistro está dispoñible en {url}",
|
||||
"log_app_config_apply": "Aplicar a configuración da app '{}'",
|
||||
"log_app_config_show_panel": "Mostrar o panel de configuración da app '{}'",
|
||||
"log_app_action_run": "Executar acción da app '{}'",
|
||||
"log_app_makedefault": "Converter '{}' na app por defecto",
|
||||
"log_app_upgrade": "Actualizar a app '{}'",
|
||||
"log_app_remove": "Eliminar a app '{}'",
|
||||
"log_app_install": "Instalar a app '{}'",
|
||||
"log_app_change_url": "Cambiar o URL da app '{}'",
|
||||
"log_operation_unit_unclosed_properly": "Non se pechou correctamente a unidade da operación",
|
||||
"log_does_exists": "Non hai rexistro de operación co nome '{log}', usa 'yunohost log list' para ver tódolos rexistros de operacións dispoñibles",
|
||||
"log_help_to_get_failed_log": "A operación '{desc}' non se completou. Comparte o rexistro completo da operación utilizando o comando 'yunohost log share {name}' para obter axuda",
|
||||
"log_link_to_failed_log": "Non se completou a operación '{desc}'. Por favor envía o rexistro completo desta operación <a href=\"#/tools/logs/{name}\">premendo aquí</a> para obter axuda",
|
||||
"migration_0015_start": "Comezando a migración a Buster",
|
||||
"migration_update_LDAP_schema": "Actualizando esquema LDAP...",
|
||||
"migration_ldap_rollback_success": "Sistema restablecido.",
|
||||
"migration_ldap_migration_failed_trying_to_rollback": "Non se puido migrar... intentando volver á versión anterior do sistema.",
|
||||
"migration_ldap_can_not_backup_before_migration": "O sistema de copia de apoio do sistema non se completou antes de que fallase a migración. Erro: {error}",
|
||||
"migration_ldap_backup_before_migration": "Crear copia de apoio da base de datos LDAP e axustes de apps antes de realizar a migración.",
|
||||
"migration_description_0020_ssh_sftp_permissions": "Engadir soporte para permisos SSH e SFTP",
|
||||
"migration_description_0019_extend_permissions_features": "Extender/recrear o sistema de xestión de permisos de apps",
|
||||
"migration_description_0018_xtable_to_nftable": "Migrar as regras de tráfico de rede antigas ao novo sistema nftable",
|
||||
"migration_description_0017_postgresql_9p6_to_11": "Migrar bases de datos desde PostgreSQL 9.6 a 11",
|
||||
"migration_description_0016_php70_to_php73_pools": "Migrar o ficheiros de configuración 'pool' de php7.0-fpm a php7.3",
|
||||
"migration_description_0015_migrate_to_buster": "Actualizar o sistema a Debian Buster e YunoHost 4.x",
|
||||
"migrating_legacy_permission_settings": "Migrando os axustes dos permisos anteriores...",
|
||||
"main_domain_changed": "Foi cambiado o dominio principal",
|
||||
"main_domain_change_failed": "Non se pode cambiar o dominio principal",
|
||||
"mail_unavailable": "Este enderezo de email está reservado e debería adxudicarse automáticamente á primeira usuaria",
|
||||
"mailbox_used_space_dovecot_down": "O servizo de caixa de correo Dovecot ten que estar activo se queres obter o espazo utilizado polo correo",
|
||||
"mailbox_disabled": "Desactivado email para usuaria {user}",
|
||||
"mail_forward_remove_failed": "Non se eliminou o reenvío de email '{mail}'",
|
||||
"mail_domain_unknown": "Enderezo de email non válido para o dominio '{domain}'. Usa un dominio administrado por este servidor.",
|
||||
"mail_alias_remove_failed": "Non se puido eliminar o alias de email '{mail}'",
|
||||
"log_tools_reboot": "Reiniciar o servidor",
|
||||
"log_tools_shutdown": "Apagar o servidor",
|
||||
"log_tools_upgrade": "Actualizar paquetes do sistema",
|
||||
"log_tools_postinstall": "Postinstalación do servidor YunoHost",
|
||||
"log_tools_migrations_migrate_forward": "Executar migracións",
|
||||
"log_domain_main_domain": "Facer que '{}' sexa o dominio principal",
|
||||
"log_user_permission_reset": "Restablecer permiso '{}'",
|
||||
"log_user_permission_update": "Actualizar accesos para permiso '{}'",
|
||||
"log_user_update": "Actualizar info da usuaria '{}'",
|
||||
"log_user_group_update": "Actualizar grupo '{}'",
|
||||
"log_user_group_delete": "Eliminar grupo '{}'",
|
||||
"log_user_group_create": "Crear grupo '{}'",
|
||||
"log_user_delete": "Eliminar usuaria '{}'",
|
||||
"log_user_create": "Engadir usuaria '{}'",
|
||||
"log_regen_conf": "Rexerar configuración do sistema '{}'",
|
||||
"log_letsencrypt_cert_renew": "Anovar certificado Let's Encrypt para '{}'",
|
||||
"log_selfsigned_cert_install": "Instalar certificado auto-asinado para o dominio '{}'",
|
||||
"log_permission_url": "Actualizar url relativo ao permiso '{}'",
|
||||
"migration_0015_general_warning": "Ten en conta que a migración é unha operación delicada. O equipo YunoHost esforzouse revisando e comprobandoa, aínda así algo podería fallar en partes do teu sistema ou as súas apps.\n\nPor tanto, é recomendable:\n- realiza unha copia de apoio de tódolos datos ou apps importantes. Máis info en https://yunohost.org/backup;\n - ten paciencia tras iniciar o proceso: dependendo da túa conexión de internet e hardware podería demorar varias horas a actualización de tódolos compoñentes.",
|
||||
"migration_0015_system_not_fully_up_to_date": "O teu sistema non está ao día. Realiza unha actualización común antes de realizar a migración a Buster.",
|
||||
"migration_0015_not_enough_free_space": "Queda moi pouco espazo en /var/! Deberías ter polo menos 1GB libre para realizar a migración.",
|
||||
"migration_0015_not_stretch": "A distribución Debian actual non é Stretch!",
|
||||
"migration_0015_yunohost_upgrade": "Iniciando a actualización do núcleo YunoHost...",
|
||||
"migration_0015_still_on_stretch_after_main_upgrade": "Algo foi mal durante a actualiza ión principal, o sistema semella que aínda está en Debian Stretch",
|
||||
"migration_0015_main_upgrade": "Iniciando a actualización principal...",
|
||||
"migration_0015_patching_sources_list": "Correxindo os sources.lists...",
|
||||
"migrations_already_ran": "Xa se realizaron estas migracións: {ids}",
|
||||
"migration_0019_slapd_config_will_be_overwritten": "Semella que editaches manualmente a configuración slapd. Para esta migración crítica YunoHost precisa forzar a actualización da configuración slapd. Os ficheiros orixinais van ser copiados en {conf_backup_folder}.",
|
||||
"migration_0019_add_new_attributes_in_ldap": "Engadir novos atributos para os permisos na base de datos LDAP",
|
||||
"migration_0018_failed_to_reset_legacy_rules": "Fallou o restablecemento das regras antigas de iptables: {error}",
|
||||
"migration_0018_failed_to_migrate_iptables_rules": "Fallou a migración das regras antigas de iptables a nftables: {error}",
|
||||
"migration_0017_not_enough_space": "Crea espazo suficiente en {path} para executar a migración.",
|
||||
"migration_0017_postgresql_11_not_installed": "PostgreSQL 9.6 está instado, pero non postgresql 11? Algo raro debeu acontecer no teu sistema :(...",
|
||||
"migration_0017_postgresql_96_not_installed": "PostgreSQL non está instalado no teu sistema. Nada que facer.",
|
||||
"migration_0015_weak_certs": "Os seguintes certificados están a utilizar algoritmos de sinatura débiles e teñen que ser actualizados para ser compatibles coa seguinte versión de nginx: {certs}",
|
||||
"migration_0015_cleaning_up": "Limpando a caché e paquetes que xa non son útiles...",
|
||||
"migration_0015_specific_upgrade": "Iniciando a actualización dos paquetes do sistema que precisan ser actualizados de xeito independente...",
|
||||
"migration_0015_modified_files": "Ten en conta que os seguintes ficheiros semella que foron modificados manualmente e poderían ser sobrescritos na actualización: {manually_modified_files}",
|
||||
"migration_0015_problematic_apps_warning": "Ten en conta que se detectaron as seguintes apps que poderían ser problemáticas. Semella que non foron instaladas usando o catálogo de YunoHost, ou non están marcadas como 'funcionais'. En consecuencia, non se pode garantir que seguirán funcionando após a actualización: {problematic_apps}",
|
||||
"diagnosis_http_localdomain": "O dominio {domain}, cun TLD .local, non é de agardar que sexa accesible desde o exterior da rede local.",
|
||||
"diagnosis_dns_specialusedomain": "O dominio {domain} baséase un dominio de nivel alto e uso especial (TLD) polo que non é de agardar que realmente teña rexistros DNS.",
|
||||
"upnp_enabled": "UPnP activado",
|
||||
"upnp_disabled": "UPnP desactivado",
|
||||
"permission_creation_failed": "Non se creou o permiso '{permission}': {error}",
|
||||
"permission_created": "Creado o permiso '{permission}'",
|
||||
"permission_cannot_remove_main": "Non está permitido eliminar un permiso principal",
|
||||
"permission_already_up_to_date": "Non se actualizou o permiso porque as solicitudes de adición/retirada xa coinciden co estado actual.",
|
||||
"permission_already_exist": "Xa existe o permiso '{permission}'",
|
||||
"permission_already_disallowed": "O grupo '{group}' xa ten o permiso '{permission}' desactivado",
|
||||
"permission_already_allowed": "O grupo '{group}' xa ten o permiso '{permission}' activado",
|
||||
"pattern_password_app": "Lamentámolo, os contrasinais non poden conter os seguintes caracteres: {forbidden_chars}",
|
||||
"pattern_username": "Só admite caracteres alfanuméricos en minúscula e trazo baixo",
|
||||
"pattern_positive_number": "Ten que ser un número positivo",
|
||||
"pattern_port_or_range": "Debe ser un número válido de porto (entre 0-65535) ou rango de portos (ex. 100:200)",
|
||||
"pattern_password": "Ten que ter polo menos 3 caracteres",
|
||||
"pattern_mailbox_quota": "Ten que ser un tamaño co sufixo b/k/M/G/T ou 0 para non ter unha cota",
|
||||
"pattern_lastname": "Ten que ser un apelido válido",
|
||||
"pattern_firstname": "Ten que ser un nome válido",
|
||||
"pattern_email": "Ten que ser un enderezo de email válido, sen o símbolo '+' (ex. persoa@exemplo.com)",
|
||||
"pattern_email_forward": "Ten que ser un enderezo de email válido, está aceptado o símbolo '+' (ex. persoa+etiqueta@exemplo.com)",
|
||||
"pattern_domain": "Ten que ser un nome de dominio válido (ex. dominiopropio.org)",
|
||||
"pattern_backup_archive_name": "Ten que ser un nome de ficheiro válido con 30 caracteres como máximo, alfanuméricos ou só caracteres -_.",
|
||||
"password_too_simple_4": "O contrasinal debe ter 12 caracteres como mínimo e conter un díxito, maiúsculas, minúsculas e caracteres especiais",
|
||||
"password_too_simple_3": "O contrasinal debe ter 8 caracteres como mínimo e conter un díxito, maiúsculas, minúsculas e caracteres especiais",
|
||||
"password_too_simple_2": "O contrasinal debe ter 8 caracteres como mínimo e conter un díxito, maiúsculas e minúsculas",
|
||||
"password_listed": "Este contrasinal está entre os máis utilizados no mundo. Por favor elixe outro que sexa máis orixinal.",
|
||||
"packages_upgrade_failed": "Non se puideron actualizar tódolos paquetes",
|
||||
"operation_interrupted": "Foi interrumpida manualmente a operación?",
|
||||
"invalid_number": "Ten que ser un número",
|
||||
"not_enough_disk_space": "Non hai espazo libre abondo en '{path}'",
|
||||
"migrations_to_be_ran_manually": "A migración {id} ten que ser executada manualmente. Vaite a Ferramentas → Migracións na páxina webadmin, ou executa `yunohost tools migrations run`.",
|
||||
"migrations_success_forward": "Migración {id} completada",
|
||||
"migrations_skip_migration": "Omitindo migración {id}...",
|
||||
"migrations_running_forward": "Realizando migración {id}...",
|
||||
"migrations_pending_cant_rerun": "Esas migracións están pendentes, polo que non ser executadas outra vez: {ids}",
|
||||
"migrations_not_pending_cant_skip": "Esas migracións non están pendentes, polo que non poden ser omitidas: {ids}",
|
||||
"migrations_no_such_migration": "Non hai migración co nome '{id}'",
|
||||
"migrations_no_migrations_to_run": "Sen migracións a executar",
|
||||
"migrations_need_to_accept_disclaimer": "Para executar a migración {id}, tes que aceptar o seguinte aviso:\n---\n{disclaimer}\n---\nSe aceptas executar a migración, por favor volve a executar o comando coa opción '--accept-disclaimer'.",
|
||||
"migrations_must_provide_explicit_targets": "Debes proporcionar obxectivos explícitos ao utilizar '--skip' ou '--force-rerun'",
|
||||
"migrations_migration_has_failed": "A migración {id} non se completou, abortando. Erro: {exception}",
|
||||
"migrations_loading_migration": "Cargando a migración {id}...",
|
||||
"migrations_list_conflict_pending_done": "Non podes usar ao mesmo tempo '--previous' e '--done'.",
|
||||
"migrations_exclusive_options": "'--auto', '--skip', e '--force-rerun' son opcións que se exclúen unhas a outras.",
|
||||
"migrations_failed_to_load_migration": "Non se cargou a migración {id}: {error}",
|
||||
"migrations_dependencies_not_satisfied": "Executar estas migracións: '{dependencies_id}', antes da migración {id}.",
|
||||
"migrations_cant_reach_migration_file": "Non se pode acceder aos ficheiros de migración na ruta '%s'",
|
||||
"regenconf_file_manually_removed": "O ficheiro de configuración '{conf}' foi eliminado manualmente e non será creado",
|
||||
"regenconf_file_manually_modified": "O ficheiro de configuración '{conf}' foi modificado manualmente e non vai ser actualizado",
|
||||
"regenconf_file_kept_back": "Era de agardar que o ficheiro de configuración '{conf}' fose eliminado por regen-conf (categoría {category}) mais foi mantido.",
|
||||
"regenconf_file_copy_failed": "Non se puido copiar o novo ficheiro de configuración '{new}' a '{conf}'",
|
||||
"regenconf_file_backed_up": "Ficheiro de configuración '{conf}' copiado a '{backup}'",
|
||||
"postinstall_low_rootfsspace": "O sistema de ficheiros raiz ten un espazo total menor de 10GB, que é pouco! Probablemente vas quedar sen espazo moi pronto! É recomendable ter polo menos 16GB para o sistema raíz. Se queres instalar YunoHost obviando este aviso, volve a executar a postinstalación con --force-diskspace",
|
||||
"port_already_opened": "O porto {port:d} xa está aberto para conexións {ip_version}",
|
||||
"port_already_closed": "O porto {port:d} xa está pechado para conexións {ip_version}",
|
||||
"permission_require_account": "O permiso {permission} só ten sentido para usuarias cunha conta, e por tanto non pode concederse a visitantes.",
|
||||
"permission_protected": "O permiso {permission} está protexido. Non podes engadir ou eliminar o grupo visitantes a/de este permiso.",
|
||||
"permission_updated": "Permiso '{permission}' actualizado",
|
||||
"permission_update_failed": "Non se actualizou o permiso '{permission}': {error}",
|
||||
"permission_not_found": "Non se atopa o permiso '{permission}'",
|
||||
"permission_deletion_failed": "Non se puido eliminar o permiso '{permission}': {error}",
|
||||
"permission_deleted": "O permiso '{permission}' foi eliminado",
|
||||
"permission_cant_add_to_all_users": "O permiso {permission} non pode ser concecido a tódalas usuarias.",
|
||||
"permission_currently_allowed_for_all_users": "Este permiso está concedido actualmente a tódalas usuarias ademáis de a outros grupos. Probablemente queiras ben eliminar o permiso 'all_users' ou ben eliminar os outros grupos que teñen permiso."
|
||||
}
|
|
@ -30,7 +30,7 @@
|
|||
"app_not_correctly_installed": "{app} sembra di non essere installata correttamente",
|
||||
"app_not_properly_removed": "{app} non è stata correttamente rimossa",
|
||||
"action_invalid": "L'azione '{action}' non è valida",
|
||||
"app_removed": "{app} rimossa",
|
||||
"app_removed": "{app} disinstallata",
|
||||
"app_sources_fetch_failed": "Impossibile riportare i file sorgenti, l'URL è corretto?",
|
||||
"app_upgrade_failed": "Impossibile aggiornare {app}: {error}",
|
||||
"app_upgraded": "{app} aggiornata",
|
||||
|
@ -428,7 +428,6 @@
|
|||
"service_description_fail2ban": "Ti protegge dal brute-force e altri tipi di attacchi da Internet",
|
||||
"service_description_dovecot": "Consente ai client mail di accedere/recuperare le email (via IMAP e POP3)",
|
||||
"service_description_dnsmasq": "Gestisce la risoluzione dei domini (DNS)",
|
||||
"service_description_avahi-daemon": "Consente di raggiungere il tuo server eseguendo 'yunohost.local' sulla tua LAN",
|
||||
"server_reboot_confirm": "Il server si riavvierà immediatamente, sei sicuro? [{answers}]",
|
||||
"server_reboot": "Il server si riavvierà",
|
||||
"server_shutdown_confirm": "Il server si spegnerà immediatamente, sei sicuro? [{answers}]",
|
||||
|
@ -631,5 +630,9 @@
|
|||
"diagnosis_sshd_config_inconsistent": "Sembra che la porta SSH sia stata modificata manualmente in /etc/ssh/sshd_config: A partire da YunoHost 4.2, una nuova configurazione globale 'security.ssh.port' è disponibile per evitare di modificare manualmente la configurazione.",
|
||||
"diagnosis_sshd_config_insecure": "Sembra che la configurazione SSH sia stata modificata manualmente, ed non è sicuro dato che non contiene le direttive 'AllowGroups' o 'Allowusers' che limitano l'accesso agli utenti autorizzati.",
|
||||
"backup_create_size_estimation": "L'archivio conterrà circa {size} di dati.",
|
||||
"app_restore_script_failed": "C'è stato un errore all'interno dello script di recupero"
|
||||
"app_restore_script_failed": "C'è stato un errore all'interno dello script di recupero",
|
||||
"global_settings_setting_security_webadmin_allowlist": "Indirizzi IP con il permesso di accedere al webadmin, separati da virgola.",
|
||||
"global_settings_setting_security_webadmin_allowlist_enabled": "Permetti solo ad alcuni IP di accedere al webadmin.",
|
||||
"disk_space_not_sufficient_update": "Non c'è abbastanza spazio libero per aggiornare questa applicazione",
|
||||
"disk_space_not_sufficient_install": "Non c'è abbastanza spazio libero per installare questa applicazione"
|
||||
}
|
|
@ -193,7 +193,6 @@
|
|||
"user_unknown": "Utilizaire « {user} » desconegut",
|
||||
"user_update_failed": "Modificacion impossibla de l’utilizaire",
|
||||
"user_updated": "L’utilizaire es estat modificat",
|
||||
"service_description_avahi-daemon": "permet d’aténher vòstre servidor via yunohost.local sus vòstre ret local",
|
||||
"service_description_dnsmasq": "gerís la resolucion dels noms de domeni (DNS)",
|
||||
"updating_apt_cache": "Actualizacion de la lista dels paquets disponibles…",
|
||||
"server_reboot_confirm": "Lo servidor es per reaviar sul pic, o volètz vertadièrament ? {answers}",
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"action_invalid": "Acção Inválida '{action}'",
|
||||
"admin_password": "Senha de administração",
|
||||
"admin_password_change_failed": "Não é possível alterar a senha",
|
||||
"admin_password_change_failed": "Não foi possível alterar a senha",
|
||||
"admin_password_changed": "A senha da administração foi alterada",
|
||||
"app_already_installed": "{app} já está instalada",
|
||||
"app_extraction_failed": "Não foi possível extrair os ficheiros para instalação",
|
||||
"app_id_invalid": "A ID da aplicação é inválida",
|
||||
"app_install_files_invalid": "Ficheiros para instalação corrompidos",
|
||||
"app_extraction_failed": "Não foi possível extrair os arquivos para instalação",
|
||||
"app_id_invalid": "App ID invaĺido",
|
||||
"app_install_files_invalid": "Esses arquivos não podem ser instalados",
|
||||
"app_manifest_invalid": "Manifesto da aplicação inválido: {error}",
|
||||
"app_not_installed": "{app} não está instalada",
|
||||
"app_removed": "{app} removida com êxito",
|
||||
|
@ -108,12 +108,12 @@
|
|||
"backup_hook_unknown": "Gancho de backup '{hook}' desconhecido",
|
||||
"backup_nothings_done": "Não há nada para guardar",
|
||||
"backup_output_directory_forbidden": "Diretório de saída proibido. Os backups não podem ser criados em /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives subpastas",
|
||||
"app_already_installed_cant_change_url": "Este aplicativo já está instalado. A URL não pode ser alterada apenas por esta função. Olhe para o `app changeurl` se estiver disponível.",
|
||||
"app_already_installed_cant_change_url": "Este aplicativo já está instalado. A URL não pode ser alterada apenas por esta função. Confira em `app changeurl` se está disponível.",
|
||||
"app_already_up_to_date": "{app} já está atualizado",
|
||||
"app_argument_choice_invalid": "Escolha inválida para o argumento '{name}', deve ser um dos {choices}",
|
||||
"app_argument_invalid": "Valor inválido de argumento '{name}': {error}",
|
||||
"app_argument_choice_invalid": "Use uma das opções '{choices}' para o argumento '{name}'",
|
||||
"app_argument_invalid": "Escolha um valor válido para o argumento '{name}': {error}",
|
||||
"app_argument_required": "O argumento '{name}' é obrigatório",
|
||||
"app_change_url_failed_nginx_reload": "Falha ao reiniciar o nginx. Aqui está o retorno de 'nginx -t':\n{nginx_errors}",
|
||||
"app_change_url_failed_nginx_reload": "Não foi possível reiniciar o nginx. Aqui está o retorno de 'nginx -t':\n{nginx_errors}",
|
||||
"app_location_unavailable": "Esta url não está disponível ou está em conflito com outra aplicação já instalada",
|
||||
"app_upgrade_app_name": "Atualizando aplicação {app}…",
|
||||
"app_upgrade_some_app_failed": "Não foi possível atualizar algumas aplicações",
|
||||
|
@ -129,5 +129,16 @@
|
|||
"app_change_url_identical_domains": "O antigo e o novo domínio / url_path são idênticos ('{domain}{path}'), nada para fazer.",
|
||||
"password_too_simple_1": "A senha precisa ter pelo menos 8 caracteres",
|
||||
"admin_password_too_long": "Escolha uma senha que contenha menos de 127 caracteres",
|
||||
"aborting": "Abortando."
|
||||
"aborting": "Abortando.",
|
||||
"app_change_url_no_script": "A aplicação '{app_name}' ainda não permite modificar a URL. Talvez devesse atualizá-la.",
|
||||
"app_argument_password_no_default": "Erro ao interpretar argumento da senha '{name}': O argumento da senha não pode ter um valor padrão por segurança",
|
||||
"app_action_cannot_be_ran_because_required_services_down": "Estes serviços devem estar funcionado para executar esta ação: {services}. Tente reiniciá-los para continuar (e possivelmente investigar o porquê de não estarem funcionado).",
|
||||
"app_action_broke_system": "Esta ação parece ter quebrado estes serviços importantes: {services}",
|
||||
"already_up_to_date": "Nada a ser feito. Tudo já está atualizado.",
|
||||
"additional_urls_already_removed": "A URL adicional '{url}'já está removida para a permissão '{permission}'",
|
||||
"additional_urls_already_added": "A URL adicional '{url}' já está adicionada para a permissão '{permission}'",
|
||||
"app_install_script_failed": "Ocorreu um erro dentro do script de instalação do aplicativo",
|
||||
"app_install_failed": "Não foi possível instalar {app}: {error}",
|
||||
"app_full_domain_unavailable": "Desculpe, esse app deve ser instalado num domínio próprio mas já há outros apps instalados no domínio '{domain}'. Você pode usar um subdomínio dedicado a esse aplicativo.",
|
||||
"app_change_url_success": "A URL agora é {domain}{path}"
|
||||
}
|
|
@ -1 +1,36 @@
|
|||
{}
|
||||
{
|
||||
"app_manifest_install_ask_domain": "Оберіть домен, в якому треба встановити цей застосунок",
|
||||
"app_manifest_invalid": "Щось не так з маніфестом застосунку: {error}",
|
||||
"app_location_unavailable": "Ця URL-адреса або недоступна, або конфліктує з уже встановленим застосунком (застосунками):\n{apps}",
|
||||
"app_label_deprecated": "Ця команда застаріла! Будь ласка, використовуйте нову команду 'yunohost user permission update' для управління заголовком застосунку.",
|
||||
"app_make_default_location_already_used": "Неможливо зробити '{app}' типовим застосунком на домені, '{domain}' вже використовується '{other_app}'",
|
||||
"app_install_script_failed": "Сталася помилка в скрипті встановлення застосунку",
|
||||
"app_install_failed": "Неможливо встановити {app}: {error}",
|
||||
"app_install_files_invalid": "Ці файли не можуть бути встановлені",
|
||||
"app_id_invalid": "Неприпустимий ID застосунку",
|
||||
"app_full_domain_unavailable": "Вибачте, цей застосунок повинен бути встановлений на власному домені, але інші застосунки вже встановлені на домені '{domain}'. Замість цього ви можете використовувати піддомен, призначений для цього застосунку.",
|
||||
"app_extraction_failed": "Не вдалося витягти файли встановлення",
|
||||
"app_change_url_success": "URL-адреса {app} тепер {domain}{path}",
|
||||
"app_change_url_no_script": "Застосунок '{app_name}' поки не підтримує зміну URL-адрес. Можливо, вам слід оновити його.",
|
||||
"app_change_url_identical_domains": "Старий і новий domain/url_path збігаються ('{domain}{path}'), нічого робити не треба.",
|
||||
"app_change_url_failed_nginx_reload": "Не вдалося перезавантажити NGINX. Ось результат 'nginx -t':\n{nginx_errors}",
|
||||
"app_argument_required": "Аргумент '{name}' необхідний",
|
||||
"app_argument_password_no_default": "Помилка під час розбору аргументу пароля '{name}': аргумент пароля не може мати типове значення з причин безпеки",
|
||||
"app_argument_invalid": "Виберіть правильне значення для аргументу '{name}': {error}",
|
||||
"app_argument_choice_invalid": "Використовуйте один з цих варіантів '{choices}' для аргументу '{name}'",
|
||||
"app_already_up_to_date": "{app} має найостаннішу версію",
|
||||
"app_already_installed_cant_change_url": "Цей застосунок уже встановлено. URL-адреса не може бути змінена тільки цією функцією. Перевірте в `app changeurl`, якщо вона доступна.",
|
||||
"app_already_installed": "{app} уже встановлено",
|
||||
"app_action_broke_system": "Ця дія, схоже, порушила роботу наступних важливих служб: {services}",
|
||||
"app_action_cannot_be_ran_because_required_services_down": "Для виконання цієї дії повинні бути запущені наступні необхідні служби: {services}. Спробуйте перезапустити їх, щоб продовжити (і, можливо, з'ясувати, чому вони не працюють).",
|
||||
"already_up_to_date": "Нічого не потрібно робити. Все вже актуально.",
|
||||
"admin_password_too_long": "Будь ласка, виберіть пароль коротше 127 символів",
|
||||
"admin_password_changed": "Пароль адміністратора було змінено",
|
||||
"admin_password_change_failed": "Неможливо змінити пароль",
|
||||
"admin_password": "Пароль адміністратора",
|
||||
"additional_urls_already_removed": "Додаткова URL-адреса '{url}' вже видалена в додатковій URL-адресі для дозволу '{permission}'",
|
||||
"additional_urls_already_added": "Додаткова URL-адреса '{url}' вже додана в додаткову URL-адресу для дозволу '{permission}'",
|
||||
"action_invalid": "Неприпустима дія '{action}'",
|
||||
"aborting": "Переривання.",
|
||||
"diagnosis_description_web": "Мережа"
|
||||
}
|
|
@ -226,7 +226,6 @@
|
|||
"service_description_fail2ban": "防止来自互联网的暴力攻击和其他类型的攻击",
|
||||
"service_description_dovecot": "允许电子邮件客户端访问/获取电子邮件(通过IMAP和POP3)",
|
||||
"service_description_dnsmasq": "处理域名解析(DNS)",
|
||||
"service_description_avahi-daemon": "允许您使用本地网络中的“ yunohost.local”访问服务器",
|
||||
"service_started": "服务 '{service}' 已启动",
|
||||
"service_start_failed": "无法启动服务 '{service}'\n\n最近的服务日志:{logs}",
|
||||
"service_reloaded_or_restarted": "服务'{service}'已重新加载或重新启动",
|
||||
|
|
|
@ -38,8 +38,8 @@ import tempfile
|
|||
import readline
|
||||
from collections import OrderedDict
|
||||
|
||||
from moulinette import msignals, m18n, msettings
|
||||
from moulinette.interfaces.cli import colorize
|
||||
from moulinette import Moulinette, m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.network import download_json
|
||||
|
@ -195,7 +195,8 @@ def app_info(app, full=False):
|
|||
|
||||
_assert_is_installed(app)
|
||||
|
||||
local_manifest = _get_manifest_of_app(os.path.join(APPS_SETTING_PATH, app))
|
||||
setting_path = os.path.join(APPS_SETTING_PATH, app)
|
||||
local_manifest = _get_manifest_of_app(setting_path)
|
||||
permissions = user_permission_list(full=True, absolute_urls=True, apps=[app])[
|
||||
"permissions"
|
||||
]
|
||||
|
@ -214,6 +215,7 @@ def app_info(app, full=False):
|
|||
if not full:
|
||||
return ret
|
||||
|
||||
ret["setting_path"] = setting_path
|
||||
ret["manifest"] = local_manifest
|
||||
ret["manifest"]["arguments"] = _set_default_ask_questions(
|
||||
ret["manifest"].get("arguments", {})
|
||||
|
@ -224,11 +226,11 @@ def app_info(app, full=False):
|
|||
ret["from_catalog"] = _load_apps_catalog()["apps"].get(absolute_app_name, {})
|
||||
ret["upgradable"] = _app_upgradable(ret)
|
||||
ret["supports_change_url"] = os.path.exists(
|
||||
os.path.join(APPS_SETTING_PATH, app, "scripts", "change_url")
|
||||
os.path.join(setting_path, "scripts", "change_url")
|
||||
)
|
||||
ret["supports_backup_restore"] = os.path.exists(
|
||||
os.path.join(APPS_SETTING_PATH, app, "scripts", "backup")
|
||||
) and os.path.exists(os.path.join(APPS_SETTING_PATH, app, "scripts", "restore"))
|
||||
os.path.join(setting_path, "scripts", "backup")
|
||||
) and os.path.exists(os.path.join(setting_path, "scripts", "restore"))
|
||||
ret["supports_multi_instance"] = is_true(
|
||||
local_manifest.get("multi_instance", False)
|
||||
)
|
||||
|
@ -503,7 +505,7 @@ def app_change_url(operation_logger, app, domain, path):
|
|||
hook_callback("post_app_change_url", env=env_dict)
|
||||
|
||||
|
||||
def app_upgrade(app=[], url=None, file=None, force=False):
|
||||
def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False):
|
||||
"""
|
||||
Upgrade app
|
||||
|
||||
|
@ -511,6 +513,7 @@ def app_upgrade(app=[], url=None, file=None, force=False):
|
|||
file -- Folder or tarball for upgrade
|
||||
app -- App(s) to upgrade (default all)
|
||||
url -- Git url to fetch for upgrade
|
||||
no_safety_backup -- Disable the safety backup during upgrade
|
||||
|
||||
"""
|
||||
from packaging import version
|
||||
|
@ -617,6 +620,7 @@ def app_upgrade(app=[], url=None, file=None, force=False):
|
|||
env_dict["YNH_APP_UPGRADE_TYPE"] = upgrade_type
|
||||
env_dict["YNH_APP_MANIFEST_VERSION"] = str(app_new_version)
|
||||
env_dict["YNH_APP_CURRENT_VERSION"] = str(app_current_version)
|
||||
env_dict["NO_BACKUP_UPGRADE"] = "1" if no_safety_backup else "0"
|
||||
|
||||
# We'll check that the app didn't brutally edit some system configuration
|
||||
manually_modified_files_before_install = manually_modified_files()
|
||||
|
@ -646,7 +650,7 @@ def app_upgrade(app=[], url=None, file=None, force=False):
|
|||
m18n.n("app_upgrade_failed", app=app_instance_name, error=error)
|
||||
)
|
||||
failure_message_with_debug_instructions = operation_logger.error(error)
|
||||
if msettings.get("interface") != "api":
|
||||
if Moulinette.interface.type != "api":
|
||||
dump_app_log_extract_for_debugging(operation_logger)
|
||||
# Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
|
@ -823,11 +827,11 @@ def app_install(
|
|||
def confirm_install(confirm):
|
||||
# Ignore if there's nothing for confirm (good quality app), if --force is used
|
||||
# or if request on the API (confirm already implemented on the API side)
|
||||
if confirm is None or force or msettings.get("interface") == "api":
|
||||
if confirm is None or force or Moulinette.interface.type == "api":
|
||||
return
|
||||
|
||||
if confirm in ["danger", "thirdparty"]:
|
||||
answer = msignals.prompt(
|
||||
answer = Moulinette.prompt(
|
||||
m18n.n("confirm_app_install_" + confirm, answers="Yes, I understand"),
|
||||
color="red",
|
||||
)
|
||||
|
@ -835,7 +839,7 @@ def app_install(
|
|||
raise YunohostError("aborting")
|
||||
|
||||
else:
|
||||
answer = msignals.prompt(
|
||||
answer = Moulinette.prompt(
|
||||
m18n.n("confirm_app_install_" + confirm, answers="Y/N"), color="yellow"
|
||||
)
|
||||
if answer.upper() != "Y":
|
||||
|
@ -883,7 +887,7 @@ def app_install(
|
|||
raise YunohostValidationError("disk_space_not_sufficient_install")
|
||||
|
||||
# Check ID
|
||||
if "id" not in manifest or "__" in manifest["id"]:
|
||||
if "id" not in manifest or "__" in manifest["id"] or "." in manifest["id"]:
|
||||
raise YunohostValidationError("app_id_invalid")
|
||||
|
||||
app_id = manifest["id"]
|
||||
|
@ -1010,7 +1014,7 @@ def app_install(
|
|||
error = m18n.n("app_install_script_failed")
|
||||
logger.error(m18n.n("app_install_failed", app=app_id, error=error))
|
||||
failure_message_with_debug_instructions = operation_logger.error(error)
|
||||
if msettings.get("interface") != "api":
|
||||
if Moulinette.interface.type != "api":
|
||||
dump_app_log_extract_for_debugging(operation_logger)
|
||||
# Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
|
@ -1184,12 +1188,13 @@ def dump_app_log_extract_for_debugging(operation_logger):
|
|||
|
||||
|
||||
@is_unit_operation()
|
||||
def app_remove(operation_logger, app):
|
||||
def app_remove(operation_logger, app, purge=False):
|
||||
"""
|
||||
Remove app
|
||||
|
||||
Keyword argument:
|
||||
Keyword arguments:
|
||||
app -- App(s) to delete
|
||||
purge -- Remove with all app data
|
||||
|
||||
"""
|
||||
from yunohost.hook import hook_exec, hook_remove, hook_callback
|
||||
|
@ -1227,6 +1232,7 @@ def app_remove(operation_logger, app):
|
|||
env_dict["YNH_APP_INSTANCE_NAME"] = app
|
||||
env_dict["YNH_APP_INSTANCE_NUMBER"] = str(app_instance_nb)
|
||||
env_dict["YNH_APP_MANIFEST_VERSION"] = manifest.get("version", "?")
|
||||
env_dict["YNH_APP_PURGE"] = str(purge)
|
||||
operation_logger.extra.update({"env": env_dict})
|
||||
operation_logger.flush()
|
||||
|
||||
|
@ -1515,7 +1521,7 @@ def app_setting(app, key, value=None, delete=False):
|
|||
# SET
|
||||
else:
|
||||
if key in ["redirected_urls", "redirected_regex"]:
|
||||
value = yaml.load(value)
|
||||
value = yaml.safe_load(value)
|
||||
app_settings[key] = value
|
||||
|
||||
_set_app_settings(app, app_settings)
|
||||
|
@ -1861,13 +1867,13 @@ def app_config_set(operation_logger, app, key=None, value=None, args=None):
|
|||
logger.debug("Asking unanswered question and prevalidating...")
|
||||
args_dict = {}
|
||||
for panel in config_panel.get("panel", []):
|
||||
if msettings.get('interface') == 'cli' and len(filter_key.split('.')) < 3:
|
||||
msignals.display(colorize("\n" + "=" * 40, 'purple'))
|
||||
msignals.display(colorize(f">>>> {panel['name']}", 'purple'))
|
||||
msignals.display(colorize("=" * 40, 'purple'))
|
||||
if Moulinette.get('interface') == 'cli' and len(filter_key.split('.')) < 3:
|
||||
Moulinette.display(colorize("\n" + "=" * 40, 'purple'))
|
||||
Moulinette.display(colorize(f">>>> {panel['name']}", 'purple'))
|
||||
Moulinette.display(colorize("=" * 40, 'purple'))
|
||||
for section in panel.get("sections", []):
|
||||
if msettings.get('interface') == 'cli' and len(filter_key.split('.')) < 3:
|
||||
msignals.display(colorize(f"\n# {section['name']}", 'purple'))
|
||||
if Moulinette.get('interface') == 'cli' and len(filter_key.split('.')) < 3:
|
||||
Moulinette.display(colorize(f"\n# {section['name']}", 'purple'))
|
||||
|
||||
# Check and ask unanswered questions
|
||||
args_dict.update(_parse_args_in_yunohost_format(
|
||||
|
@ -2284,7 +2290,7 @@ def _get_app_settings(app_id):
|
|||
)
|
||||
try:
|
||||
with open(os.path.join(APPS_SETTING_PATH, app_id, "settings.yml")) as f:
|
||||
settings = yaml.load(f)
|
||||
settings = yaml.safe_load(f)
|
||||
# If label contains unicode char, this may later trigger issues when building strings...
|
||||
# FIXME: this should be propagated to read_yaml so that this fix applies everywhere I think...
|
||||
settings = {k: v for k, v in settings.items()}
|
||||
|
@ -2851,12 +2857,12 @@ class YunoHostArgumentFormatParser(object):
|
|||
|
||||
while True:
|
||||
# Display question if no value filled or if it's a readonly message
|
||||
if msettings.get('interface') == 'cli':
|
||||
if Moulinette.get('interface') == 'cli':
|
||||
text_for_user_input_in_cli = self._format_text_for_user_input_in_cli(
|
||||
question
|
||||
)
|
||||
if getattr(self, "readonly", False):
|
||||
msignals.display(text_for_user_input_in_cli)
|
||||
Moulinette.display(text_for_user_input_in_cli)
|
||||
|
||||
elif question.value is None:
|
||||
prefill = None
|
||||
|
@ -2866,7 +2872,7 @@ class YunoHostArgumentFormatParser(object):
|
|||
prefill = question.default
|
||||
readline.set_startup_hook(lambda: readline.insert_text(prefill))
|
||||
try:
|
||||
question.value = msignals.prompt(
|
||||
question.value = Moulinette.prompt(
|
||||
message=text_for_user_input_in_cli,
|
||||
is_password=self.hide_user_input_in_prompt,
|
||||
confirm=self.hide_user_input_in_prompt
|
||||
|
@ -2887,9 +2893,9 @@ class YunoHostArgumentFormatParser(object):
|
|||
try:
|
||||
self._prevalidate(question)
|
||||
except YunohostValidationError as e:
|
||||
if msettings.get('interface') == 'api':
|
||||
if Moulinette.get('interface') == 'api':
|
||||
raise
|
||||
msignals.display(str(e), 'error')
|
||||
Moulinette.display(str(e), 'error')
|
||||
question.value = None
|
||||
continue
|
||||
break
|
||||
|
@ -3173,7 +3179,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser):
|
|||
@classmethod
|
||||
def clean_upload_dirs(cls):
|
||||
# Delete files uploaded from API
|
||||
if msettings.get('interface') == 'api':
|
||||
if Moulinette.get('interface') == 'api':
|
||||
for upload_dir in cls.upload_dirs:
|
||||
if os.path.exists(upload_dir):
|
||||
shutil.rmtree(upload_dir)
|
||||
|
@ -3186,7 +3192,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser):
|
|||
question_parsed.accept = question.get('accept').replace(' ', '').split(',')
|
||||
else:
|
||||
question_parsed.accept = []
|
||||
if msettings.get('interface') == 'api':
|
||||
if Moulinette.get('interface') == 'api':
|
||||
if user_answers.get(question_parsed.name):
|
||||
question_parsed.value = {
|
||||
'content': question_parsed.value,
|
||||
|
@ -3217,7 +3223,7 @@ class FileArgumentParser(YunoHostArgumentFormatParser):
|
|||
if not question.value:
|
||||
return question.value
|
||||
|
||||
if msettings.get('interface') == 'api':
|
||||
if Moulinette.get('interface') == 'api':
|
||||
|
||||
upload_dir = tempfile.mkdtemp(prefix='tmp_configpanel_')
|
||||
FileArgumentParser.upload_dirs += [upload_dir]
|
||||
|
|
68
src/yunohost/authenticators/ldap_admin.py
Normal file
68
src/yunohost/authenticators/ldap_admin.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import logging
|
||||
import ldap
|
||||
import ldap.sasl
|
||||
import time
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.authentication import BaseAuthenticator
|
||||
from yunohost.utils.error import YunohostError
|
||||
|
||||
logger = logging.getLogger("yunohost.authenticators.ldap_admin")
|
||||
|
||||
|
||||
class Authenticator(BaseAuthenticator):
|
||||
|
||||
name = "ldap_admin"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.uri = "ldap://localhost:389"
|
||||
self.basedn = "dc=yunohost,dc=org"
|
||||
self.admindn = "cn=admin,dc=yunohost,dc=org"
|
||||
|
||||
def _authenticate_credentials(self, credentials=None):
|
||||
|
||||
# TODO : change authentication format
|
||||
# to support another dn to support multi-admins
|
||||
|
||||
def _reconnect():
|
||||
con = ldap.ldapobject.ReconnectLDAPObject(
|
||||
self.uri, retry_max=10, retry_delay=0.5
|
||||
)
|
||||
con.simple_bind_s(self.admindn, credentials)
|
||||
return con
|
||||
|
||||
try:
|
||||
con = _reconnect()
|
||||
except ldap.INVALID_CREDENTIALS:
|
||||
raise YunohostError("invalid_password")
|
||||
except ldap.SERVER_DOWN:
|
||||
# ldap is down, attempt to restart it before really failing
|
||||
logger.warning(m18n.n("ldap_server_is_down_restart_it"))
|
||||
os.system("systemctl restart slapd")
|
||||
time.sleep(10) # waits 10 secondes so we are sure that slapd has restarted
|
||||
|
||||
try:
|
||||
con = _reconnect()
|
||||
except ldap.SERVER_DOWN:
|
||||
raise YunohostError("ldap_server_down")
|
||||
|
||||
# Check that we are indeed logged in with the expected identity
|
||||
try:
|
||||
# whoami_s return dn:..., then delete these 3 characters
|
||||
who = con.whoami_s()[3:]
|
||||
except Exception as e:
|
||||
logger.warning("Error during ldap authentication process: %s", e)
|
||||
raise
|
||||
else:
|
||||
if who != self.admindn:
|
||||
raise YunohostError(
|
||||
f"Not logged with the appropriate identity ? Found {who}, expected {self.admindn} !?",
|
||||
raw_msg=True,
|
||||
)
|
||||
finally:
|
||||
# Free the connection, we don't really need it to keep it open as the point is only to check authentication...
|
||||
if con:
|
||||
con.unbind_s()
|
|
@ -38,7 +38,7 @@ from collections import OrderedDict
|
|||
from functools import reduce
|
||||
from packaging import version
|
||||
|
||||
from moulinette import msignals, m18n, msettings
|
||||
from moulinette import Moulinette, m18n
|
||||
from moulinette.utils import filesystem
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml
|
||||
|
@ -1509,7 +1509,7 @@ class RestoreManager:
|
|||
m18n.n("app_restore_failed", app=app_instance_name, error=error)
|
||||
)
|
||||
failure_message_with_debug_instructions = operation_logger.error(error)
|
||||
if msettings.get("interface") != "api":
|
||||
if Moulinette.interface.type != "api":
|
||||
dump_app_log_extract_for_debugging(operation_logger)
|
||||
# Script got manually interrupted ... N.B. : KeyboardInterrupt does not inherit from Exception
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
|
@ -1840,7 +1840,7 @@ class BackupMethod(object):
|
|||
# Ask confirmation for copying
|
||||
if size > MB_ALLOWED_TO_ORGANIZE:
|
||||
try:
|
||||
i = msignals.prompt(
|
||||
i = Moulinette.prompt(
|
||||
m18n.n(
|
||||
"backup_ask_for_copying_if_needed",
|
||||
answers="y/N",
|
||||
|
@ -2344,7 +2344,7 @@ def backup_restore(name, system=[], apps=[], force=False):
|
|||
if not force:
|
||||
try:
|
||||
# Ask confirmation for restoring
|
||||
i = msignals.prompt(
|
||||
i = Moulinette.prompt(
|
||||
m18n.n("restore_confirm_yunohost_installed", answers="y/N")
|
||||
)
|
||||
except NotImplemented:
|
||||
|
@ -2418,7 +2418,7 @@ def backup_list(with_info=False, human_readable=False):
|
|||
|
||||
def backup_download(name):
|
||||
|
||||
if msettings.get("interface") != "api":
|
||||
if Moulinette.interface.type != "api":
|
||||
logger.error(
|
||||
"This option is only meant for the API/webadmin and doesn't make sense for the command line."
|
||||
)
|
||||
|
|
|
@ -28,7 +28,7 @@ import re
|
|||
import os
|
||||
import time
|
||||
|
||||
from moulinette import m18n, msettings
|
||||
from moulinette import m18n, Moulinette
|
||||
from moulinette.utils import log
|
||||
from moulinette.utils.filesystem import (
|
||||
read_json,
|
||||
|
@ -138,7 +138,7 @@ def diagnosis_show(
|
|||
url = yunopaste(content)
|
||||
|
||||
logger.info(m18n.n("log_available_on_yunopaste", url=url))
|
||||
if msettings.get("interface") == "api":
|
||||
if Moulinette.interface.type == "api":
|
||||
return {"url": url}
|
||||
else:
|
||||
return
|
||||
|
@ -219,7 +219,7 @@ def diagnosis_run(
|
|||
|
||||
if email:
|
||||
_email_diagnosis_issues()
|
||||
if issues and msettings.get("interface") == "cli":
|
||||
if issues and Moulinette.interface.type == "cli":
|
||||
logger.warning(m18n.n("diagnosis_display_tip"))
|
||||
|
||||
|
||||
|
@ -595,7 +595,7 @@ class Diagnoser:
|
|||
info[1].update(meta_data)
|
||||
s = m18n.n(info[0], **(info[1]))
|
||||
# In cli, we remove the html tags
|
||||
if msettings.get("interface") != "api" or force_remove_html_tags:
|
||||
if Moulinette.interface.type != "api" or force_remove_html_tags:
|
||||
s = s.replace("<cmd>", "'").replace("</cmd>", "'")
|
||||
s = html_tags.sub("", s.replace("<br>", "\n"))
|
||||
else:
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
from moulinette import m18n, msettings, msignals
|
||||
from moulinette import m18n, Moulinette
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
@ -166,7 +166,9 @@ def domain_add(operation_logger, domain, dyndns=False):
|
|||
# because it's one of the major service, but in the long term we
|
||||
# should identify the root of this bug...
|
||||
_force_clear_hashes(["/etc/nginx/conf.d/%s.conf" % domain])
|
||||
regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd"])
|
||||
regen_conf(
|
||||
names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd", "mdns"]
|
||||
)
|
||||
app_ssowatconf()
|
||||
|
||||
except Exception as e:
|
||||
|
@ -239,8 +241,8 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False):
|
|||
|
||||
if apps_on_that_domain:
|
||||
if remove_apps:
|
||||
if msettings.get("interface") == "cli" and not force:
|
||||
answer = msignals.prompt(
|
||||
if Moulinette.interface.type == "cli" and not force:
|
||||
answer = Moulinette.prompt(
|
||||
m18n.n(
|
||||
"domain_remove_confirm_apps_removal",
|
||||
apps="\n".join([x[1] for x in apps_on_that_domain]),
|
||||
|
@ -293,7 +295,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False):
|
|||
"/etc/nginx/conf.d/%s.conf" % domain, new_conf=None, save=True
|
||||
)
|
||||
|
||||
regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix"])
|
||||
regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd", "mdns"])
|
||||
app_ssowatconf()
|
||||
|
||||
hook_callback("post_domain_remove", args=[domain])
|
||||
|
@ -346,7 +348,7 @@ def domain_dns_conf(domain, ttl=None):
|
|||
for record in record_list:
|
||||
result += "\n{name} {ttl} IN {type} {value}".format(**record)
|
||||
|
||||
if msettings.get("interface") == "cli":
|
||||
if Moulinette.interface.type == "cli":
|
||||
logger.info(m18n.n("domain_dns_conf_is_just_a_recommendation"))
|
||||
|
||||
return result
|
||||
|
|
|
@ -179,7 +179,7 @@ def firewall_list(raw=False, by_ip_version=False, list_forwarded=False):
|
|||
|
||||
"""
|
||||
with open(FIREWALL_FILE) as f:
|
||||
firewall = yaml.load(f)
|
||||
firewall = yaml.safe_load(f)
|
||||
if raw:
|
||||
return firewall
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ import mimetypes
|
|||
from glob import iglob
|
||||
from importlib import import_module
|
||||
|
||||
from moulinette import m18n, msettings
|
||||
from moulinette import m18n, Moulinette
|
||||
from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||
from moulinette.utils import log
|
||||
from moulinette.utils.filesystem import read_yaml
|
||||
|
@ -416,7 +416,7 @@ def _hook_exec_bash(path, args, chdir, env, user, return_format, loggers):
|
|||
env = {}
|
||||
env["YNH_CWD"] = chdir
|
||||
|
||||
env["YNH_INTERFACE"] = msettings.get("interface")
|
||||
env["YNH_INTERFACE"] = Moulinette.interface.type
|
||||
|
||||
stdreturn = os.path.join(tempfile.mkdtemp(), "stdreturn")
|
||||
with open(stdreturn, "w") as f:
|
||||
|
|
|
@ -33,7 +33,7 @@ import psutil
|
|||
from datetime import datetime, timedelta
|
||||
from logging import FileHandler, getLogger, Formatter
|
||||
|
||||
from moulinette import m18n, msettings
|
||||
from moulinette import m18n, Moulinette
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||
from yunohost.utils.packages import get_ynh_package_version
|
||||
|
@ -44,7 +44,6 @@ CATEGORIES_PATH = "/var/log/yunohost/categories/"
|
|||
OPERATIONS_PATH = "/var/log/yunohost/categories/operation/"
|
||||
METADATA_FILE_EXT = ".yml"
|
||||
LOG_FILE_EXT = ".log"
|
||||
RELATED_CATEGORIES = ["app", "domain", "group", "service", "user"]
|
||||
|
||||
logger = getActionLogger("yunohost.log")
|
||||
|
||||
|
@ -125,7 +124,7 @@ def log_list(limit=None, with_details=False, with_suboperations=False):
|
|||
operations = list(reversed(sorted(operations, key=lambda o: o["name"])))
|
||||
# Reverse the order of log when in cli, more comfortable to read (avoid
|
||||
# unecessary scrolling)
|
||||
is_api = msettings.get("interface") == "api"
|
||||
is_api = Moulinette.interface.type == "api"
|
||||
if not is_api:
|
||||
operations = list(reversed(operations))
|
||||
|
||||
|
@ -214,7 +213,7 @@ def log_show(
|
|||
url = yunopaste(content)
|
||||
|
||||
logger.info(m18n.n("log_available_on_yunopaste", url=url))
|
||||
if msettings.get("interface") == "api":
|
||||
if Moulinette.interface.type == "api":
|
||||
return {"url": url}
|
||||
else:
|
||||
return
|
||||
|
@ -609,7 +608,7 @@ class OperationLogger(object):
|
|||
"operation": self.operation,
|
||||
"parent": self.parent,
|
||||
"yunohost_version": get_ynh_package_version("yunohost")["version"],
|
||||
"interface": msettings.get("interface"),
|
||||
"interface": Moulinette.interface.type,
|
||||
}
|
||||
if self.related_to is not None:
|
||||
data["related_to"] = self.related_to
|
||||
|
@ -663,7 +662,7 @@ class OperationLogger(object):
|
|||
self.logger.removeHandler(self.file_handler)
|
||||
self.file_handler.close()
|
||||
|
||||
is_api = msettings.get("interface") == "api"
|
||||
is_api = Moulinette.interface.type == "api"
|
||||
desc = _get_description_from_name(self.name)
|
||||
if error is None:
|
||||
if is_api:
|
||||
|
|
|
@ -444,7 +444,7 @@ def _get_regenconf_infos():
|
|||
"""
|
||||
try:
|
||||
with open(REGEN_CONF_FILE, "r") as f:
|
||||
return yaml.load(f)
|
||||
return yaml.safe_load(f)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
|
|
@ -37,10 +37,19 @@ from moulinette import m18n
|
|||
from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||
from moulinette.utils.process import check_output
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.filesystem import read_file, append_to_file, write_to_file
|
||||
from moulinette.utils.filesystem import (
|
||||
read_file,
|
||||
append_to_file,
|
||||
write_to_file,
|
||||
read_yaml,
|
||||
write_to_yaml,
|
||||
)
|
||||
|
||||
MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock"
|
||||
|
||||
SERVICES_CONF = "/etc/yunohost/services.yml"
|
||||
SERVICES_CONF_BASE = "/usr/share/yunohost/templates/yunohost/services.yml"
|
||||
|
||||
logger = getActionLogger("yunohost.service")
|
||||
|
||||
|
||||
|
@ -127,7 +136,8 @@ def service_add(
|
|||
|
||||
try:
|
||||
_save_services(services)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
# we'll get a logger.warning with more details in _save_services
|
||||
raise YunohostError("service_add_failed", service=name)
|
||||
|
||||
|
@ -669,17 +679,21 @@ def _get_services():
|
|||
|
||||
"""
|
||||
try:
|
||||
with open("/etc/yunohost/services.yml", "r") as f:
|
||||
services = yaml.load(f) or {}
|
||||
services = read_yaml(SERVICES_CONF_BASE) or {}
|
||||
|
||||
# These are keys flagged 'null' in the base conf
|
||||
legacy_keys_to_delete = [k for k, v in services.items() if v is None]
|
||||
|
||||
services.update(read_yaml(SERVICES_CONF) or {})
|
||||
|
||||
services = {
|
||||
name: infos
|
||||
for name, infos in services.items()
|
||||
if name not in legacy_keys_to_delete
|
||||
}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
# some services are marked as None to remove them from YunoHost
|
||||
# filter this
|
||||
for key, value in list(services.items()):
|
||||
if value is None:
|
||||
del services[key]
|
||||
|
||||
# Dirty hack to automatically find custom SSH port ...
|
||||
ssh_port_line = re.findall(
|
||||
r"\bPort *([0-9]{2,5})\b", read_file("/etc/ssh/sshd_config")
|
||||
|
@ -703,6 +717,13 @@ def _get_services():
|
|||
del services["postgresql"]["description"]
|
||||
services["postgresql"]["actual_systemd_service"] = "postgresql@11-main"
|
||||
|
||||
# Remove legacy /var/log/daemon.log and /var/log/syslog from log entries
|
||||
# because they are too general. Instead, now the journalctl log is
|
||||
# returned by default which is more relevant.
|
||||
for infos in services.values():
|
||||
if infos.get("log") in ["/var/log/syslog", "/var/log/daemon.log"]:
|
||||
del infos["log"]
|
||||
|
||||
return services
|
||||
|
||||
|
||||
|
@ -714,12 +735,26 @@ def _save_services(services):
|
|||
services -- A dict of managed services with their parameters
|
||||
|
||||
"""
|
||||
try:
|
||||
with open("/etc/yunohost/services.yml", "w") as f:
|
||||
yaml.safe_dump(services, f, default_flow_style=False)
|
||||
except Exception as e:
|
||||
logger.warning("Error while saving services, exception: %s", e, exc_info=1)
|
||||
raise
|
||||
|
||||
# Compute the diff with the base file
|
||||
# such that /etc/yunohost/services.yml contains the minimal
|
||||
# changes with respect to the base conf
|
||||
|
||||
conf_base = yaml.safe_load(open(SERVICES_CONF_BASE)) or {}
|
||||
|
||||
diff = {}
|
||||
|
||||
for service_name, service_infos in services.items():
|
||||
service_conf_base = conf_base.get(service_name, {})
|
||||
diff[service_name] = {}
|
||||
|
||||
for key, value in service_infos.items():
|
||||
if service_conf_base.get(key) != value:
|
||||
diff[service_name][key] = value
|
||||
|
||||
diff = {name: infos for name, infos in diff.items() if infos}
|
||||
|
||||
write_to_yaml(SERVICES_CONF, diff)
|
||||
|
||||
|
||||
def _tail(file, n):
|
||||
|
|
|
@ -102,6 +102,7 @@ DEFAULTS = OrderedDict(
|
|||
("ssowat.panel_overlay.enabled", {"type": "bool", "default": True}),
|
||||
("security.webadmin.allowlist.enabled", {"type": "bool", "default": False}),
|
||||
("security.webadmin.allowlist", {"type": "string", "default": ""}),
|
||||
("security.experimental.enabled", {"type": "bool", "default": False}),
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -399,6 +400,12 @@ def reconfigure_nginx(setting_name, old_value, new_value):
|
|||
regen_conf(names=["nginx"])
|
||||
|
||||
|
||||
@post_change_hook("security.experimental.enabled")
|
||||
def reconfigure_nginx_and_yunohost(setting_name, old_value, new_value):
|
||||
if old_value != new_value:
|
||||
regen_conf(names=["nginx", "yunohost"])
|
||||
|
||||
|
||||
@post_change_hook("security.ssh.compatibility")
|
||||
def reconfigure_ssh(setting_name, old_value, new_value):
|
||||
if old_value != new_value:
|
||||
|
|
|
@ -3,7 +3,7 @@ import pytest
|
|||
import sys
|
||||
|
||||
import moulinette
|
||||
from moulinette import m18n, msettings
|
||||
from moulinette import m18n, Moulinette
|
||||
from yunohost.utils.error import YunohostError
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
@ -81,4 +81,12 @@ def pytest_cmdline_main(config):
|
|||
import yunohost
|
||||
|
||||
yunohost.init(debug=config.option.yunodebug)
|
||||
msettings["interface"] = "test"
|
||||
|
||||
class DummyInterface:
|
||||
|
||||
type = "test"
|
||||
|
||||
def prompt(*args, **kwargs):
|
||||
raise NotImplementedError
|
||||
|
||||
Moulinette._interface = DummyInterface()
|
||||
|
|
|
@ -5,7 +5,7 @@ from mock import patch
|
|||
from io import StringIO
|
||||
from collections import OrderedDict
|
||||
|
||||
from moulinette import msignals
|
||||
from moulinette import Moulinette
|
||||
|
||||
from yunohost import domain, user
|
||||
from yunohost.app import _parse_args_in_yunohost_format, PasswordArgumentParser
|
||||
|
@ -84,7 +84,7 @@ def test_parse_args_in_yunohost_format_string_input():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_string": ("some_value", "string")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="some_value"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -97,7 +97,7 @@ def test_parse_args_in_yunohost_format_string_input_no_ask():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_string": ("some_value", "string")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="some_value"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -124,7 +124,7 @@ def test_parse_args_in_yunohost_format_string_optional_with_input():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_string": ("some_value", "string")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="some_value"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -139,7 +139,7 @@ def test_parse_args_in_yunohost_format_string_optional_with_empty_input():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_string": ("", "string")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value=""):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value=""):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -153,7 +153,7 @@ def test_parse_args_in_yunohost_format_string_optional_with_input_without_ask():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_string": ("some_value", "string")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="some_value"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -180,7 +180,9 @@ def test_parse_args_in_yunohost_format_string_input_test_ask():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value") as prompt:
|
||||
with patch.object(
|
||||
Moulinette.interface, "prompt", return_value="some_value"
|
||||
) as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
prompt.assert_called_with(ask_text, False)
|
||||
|
||||
|
@ -197,7 +199,9 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_default():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value") as prompt:
|
||||
with patch.object(
|
||||
Moulinette.interface, "prompt", return_value="some_value"
|
||||
) as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
prompt.assert_called_with("%s (default: %s)" % (ask_text, default_text), False)
|
||||
|
||||
|
@ -215,7 +219,9 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_example():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value") as prompt:
|
||||
with patch.object(
|
||||
Moulinette.interface, "prompt", return_value="some_value"
|
||||
) as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
assert ask_text in prompt.call_args[0][0]
|
||||
assert example_text in prompt.call_args[0][0]
|
||||
|
@ -234,7 +240,9 @@ def test_parse_args_in_yunohost_format_string_input_test_ask_with_help():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value") as prompt:
|
||||
with patch.object(
|
||||
Moulinette.interface, "prompt", return_value="some_value"
|
||||
) as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
assert ask_text in prompt.call_args[0][0]
|
||||
assert help_text in prompt.call_args[0][0]
|
||||
|
@ -251,7 +259,7 @@ def test_parse_args_in_yunohost_format_string_with_choice_prompt():
|
|||
questions = [{"name": "some_string", "type": "string", "choices": ["fr", "en"]}]
|
||||
answers = {"some_string": "fr"}
|
||||
expected_result = OrderedDict({"some_string": ("fr", "string")})
|
||||
with patch.object(msignals, "prompt", return_value="fr"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="fr"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -275,7 +283,7 @@ def test_parse_args_in_yunohost_format_string_with_choice_ask():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="ru") as prompt:
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="ru") as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
assert ask_text in prompt.call_args[0][0]
|
||||
|
||||
|
@ -333,7 +341,7 @@ def test_parse_args_in_yunohost_format_password_input():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_password": ("some_value", "password")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="some_value"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -347,7 +355,7 @@ def test_parse_args_in_yunohost_format_password_input_no_ask():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_password": ("some_value", "password")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="some_value"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -383,7 +391,7 @@ def test_parse_args_in_yunohost_format_password_optional_with_input():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_password": ("some_value", "password")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="some_value"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -399,7 +407,7 @@ def test_parse_args_in_yunohost_format_password_optional_with_empty_input():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_password": ("", "password")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value=""):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value=""):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -414,7 +422,7 @@ def test_parse_args_in_yunohost_format_password_optional_with_input_without_ask(
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_password": ("some_value", "password")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="some_value"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -462,7 +470,9 @@ def test_parse_args_in_yunohost_format_password_input_test_ask():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value") as prompt:
|
||||
with patch.object(
|
||||
Moulinette.interface, "prompt", return_value="some_value"
|
||||
) as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
prompt.assert_called_with(ask_text, True)
|
||||
|
||||
|
@ -481,7 +491,9 @@ def test_parse_args_in_yunohost_format_password_input_test_ask_with_example():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value") as prompt:
|
||||
with patch.object(
|
||||
Moulinette.interface, "prompt", return_value="some_value"
|
||||
) as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
assert ask_text in prompt.call_args[0][0]
|
||||
assert example_text in prompt.call_args[0][0]
|
||||
|
@ -501,7 +513,9 @@ def test_parse_args_in_yunohost_format_password_input_test_ask_with_help():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value") as prompt:
|
||||
with patch.object(
|
||||
Moulinette.interface, "prompt", return_value="some_value"
|
||||
) as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
assert ask_text in prompt.call_args[0][0]
|
||||
assert help_text in prompt.call_args[0][0]
|
||||
|
@ -594,7 +608,7 @@ def test_parse_args_in_yunohost_format_path_input():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_path": ("some_value", "path")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="some_value"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -608,7 +622,7 @@ def test_parse_args_in_yunohost_format_path_input_no_ask():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_path": ("some_value", "path")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="some_value"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -637,7 +651,7 @@ def test_parse_args_in_yunohost_format_path_optional_with_input():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_path": ("some_value", "path")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="some_value"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -653,7 +667,7 @@ def test_parse_args_in_yunohost_format_path_optional_with_empty_input():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_path": ("", "path")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value=""):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value=""):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -668,7 +682,7 @@ def test_parse_args_in_yunohost_format_path_optional_with_input_without_ask():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_path": ("some_value", "path")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="some_value"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -697,7 +711,9 @@ def test_parse_args_in_yunohost_format_path_input_test_ask():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value") as prompt:
|
||||
with patch.object(
|
||||
Moulinette.interface, "prompt", return_value="some_value"
|
||||
) as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
prompt.assert_called_with(ask_text, False)
|
||||
|
||||
|
@ -715,7 +731,9 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_default():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value") as prompt:
|
||||
with patch.object(
|
||||
Moulinette.interface, "prompt", return_value="some_value"
|
||||
) as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
prompt.assert_called_with("%s (default: %s)" % (ask_text, default_text), False)
|
||||
|
||||
|
@ -734,7 +752,9 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_example():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value") as prompt:
|
||||
with patch.object(
|
||||
Moulinette.interface, "prompt", return_value="some_value"
|
||||
) as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
assert ask_text in prompt.call_args[0][0]
|
||||
assert example_text in prompt.call_args[0][0]
|
||||
|
@ -754,7 +774,9 @@ def test_parse_args_in_yunohost_format_path_input_test_ask_with_help():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="some_value") as prompt:
|
||||
with patch.object(
|
||||
Moulinette.interface, "prompt", return_value="some_value"
|
||||
) as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
assert ask_text in prompt.call_args[0][0]
|
||||
assert help_text in prompt.call_args[0][0]
|
||||
|
@ -918,11 +940,11 @@ def test_parse_args_in_yunohost_format_boolean_input():
|
|||
answers = {}
|
||||
|
||||
expected_result = OrderedDict({"some_boolean": (1, "boolean")})
|
||||
with patch.object(msignals, "prompt", return_value="y"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="y"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
expected_result = OrderedDict({"some_boolean": (0, "boolean")})
|
||||
with patch.object(msignals, "prompt", return_value="n"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="n"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -936,7 +958,7 @@ def test_parse_args_in_yunohost_format_boolean_input_no_ask():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_boolean": (1, "boolean")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="y"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="y"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -965,7 +987,7 @@ def test_parse_args_in_yunohost_format_boolean_optional_with_input():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_boolean": (1, "boolean")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="y"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="y"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -981,7 +1003,7 @@ def test_parse_args_in_yunohost_format_boolean_optional_with_empty_input():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_boolean": (0, "boolean")}) # default to false
|
||||
|
||||
with patch.object(msignals, "prompt", return_value=""):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value=""):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -996,7 +1018,7 @@ def test_parse_args_in_yunohost_format_boolean_optional_with_input_without_ask()
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_boolean": (0, "boolean")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="n"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="n"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -1039,7 +1061,7 @@ def test_parse_args_in_yunohost_format_boolean_input_test_ask():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value=0) as prompt:
|
||||
with patch.object(Moulinette.interface, "prompt", return_value=0) as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
prompt.assert_called_with(ask_text + " [yes | no] (default: no)", False)
|
||||
|
||||
|
@ -1057,7 +1079,7 @@ def test_parse_args_in_yunohost_format_boolean_input_test_ask_with_default():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value=1) as prompt:
|
||||
with patch.object(Moulinette.interface, "prompt", return_value=1) as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
prompt.assert_called_with("%s [yes | no] (default: yes)" % ask_text, False)
|
||||
|
||||
|
@ -1193,11 +1215,11 @@ def test_parse_args_in_yunohost_format_domain_two_domains_default_input():
|
|||
domain, "_get_maindomain", return_value=main_domain
|
||||
), patch.object(domain, "domain_list", return_value={"domains": domains}):
|
||||
expected_result = OrderedDict({"some_domain": (main_domain, "domain")})
|
||||
with patch.object(msignals, "prompt", return_value=main_domain):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value=main_domain):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
expected_result = OrderedDict({"some_domain": (other_domain, "domain")})
|
||||
with patch.object(msignals, "prompt", return_value=other_domain):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value=other_domain):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -1380,14 +1402,14 @@ def test_parse_args_in_yunohost_format_user_two_users_default_input():
|
|||
with patch.object(user, "user_list", return_value={"users": users}):
|
||||
with patch.object(user, "user_info", return_value={}):
|
||||
expected_result = OrderedDict({"some_user": (username, "user")})
|
||||
with patch.object(msignals, "prompt", return_value=username):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value=username):
|
||||
assert (
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
== expected_result
|
||||
)
|
||||
|
||||
expected_result = OrderedDict({"some_user": (other_user, "user")})
|
||||
with patch.object(msignals, "prompt", return_value=other_user):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value=other_user):
|
||||
assert (
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
== expected_result
|
||||
|
@ -1447,14 +1469,14 @@ def test_parse_args_in_yunohost_format_number_input():
|
|||
answers = {}
|
||||
|
||||
expected_result = OrderedDict({"some_number": (1337, "number")})
|
||||
with patch.object(msignals, "prompt", return_value="1337"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="1337"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
with patch.object(msignals, "prompt", return_value=1337):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value=1337):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
expected_result = OrderedDict({"some_number": (0, "number")})
|
||||
with patch.object(msignals, "prompt", return_value=""):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value=""):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -1468,7 +1490,7 @@ def test_parse_args_in_yunohost_format_number_input_no_ask():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_number": (1337, "number")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="1337"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="1337"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -1497,7 +1519,7 @@ def test_parse_args_in_yunohost_format_number_optional_with_input():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_number": (1337, "number")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="1337"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="1337"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -1512,7 +1534,7 @@ def test_parse_args_in_yunohost_format_number_optional_with_input_without_ask():
|
|||
answers = {}
|
||||
expected_result = OrderedDict({"some_number": (0, "number")})
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="0"):
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="0"):
|
||||
assert _parse_args_in_yunohost_format(answers, questions) == expected_result
|
||||
|
||||
|
||||
|
@ -1555,7 +1577,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="1111") as prompt:
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
prompt.assert_called_with("%s (default: 0)" % (ask_text), False)
|
||||
|
||||
|
@ -1573,7 +1595,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask_with_default():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="1111") as prompt:
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
prompt.assert_called_with("%s (default: %s)" % (ask_text, default_value), False)
|
||||
|
||||
|
@ -1592,7 +1614,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask_with_example():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="1111") as prompt:
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
assert ask_text in prompt.call_args[0][0]
|
||||
assert example_value in prompt.call_args[0][0]
|
||||
|
@ -1612,7 +1634,7 @@ def test_parse_args_in_yunohost_format_number_input_test_ask_with_help():
|
|||
]
|
||||
answers = {}
|
||||
|
||||
with patch.object(msignals, "prompt", return_value="1111") as prompt:
|
||||
with patch.object(Moulinette.interface, "prompt", return_value="1111") as prompt:
|
||||
_parse_args_in_yunohost_format(answers, questions)
|
||||
assert ask_text in prompt.call_args[0][0]
|
||||
assert help_value in prompt.call_args[0][0]
|
||||
|
|
59
src/yunohost/tests/test_ldapauth.py
Normal file
59
src/yunohost/tests/test_ldapauth.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
import pytest
|
||||
import os
|
||||
|
||||
from yunohost.authenticators.ldap_admin import Authenticator as LDAPAuth
|
||||
from yunohost.tools import tools_adminpw
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
|
||||
if os.system("systemctl is-active slapd") != 0:
|
||||
os.system("systemctl start slapd && sleep 3")
|
||||
|
||||
tools_adminpw("yunohost", check_strength=False)
|
||||
|
||||
|
||||
def test_authenticate():
|
||||
LDAPAuth().authenticate_credentials(credentials="yunohost")
|
||||
|
||||
|
||||
def test_authenticate_with_wrong_password():
|
||||
with pytest.raises(MoulinetteError) as exception:
|
||||
LDAPAuth().authenticate_credentials(credentials="bad_password_lul")
|
||||
|
||||
translation = m18n.n("invalid_password")
|
||||
expected_msg = translation.format()
|
||||
assert expected_msg in str(exception)
|
||||
|
||||
|
||||
def test_authenticate_server_down(mocker):
|
||||
os.system("systemctl stop slapd && sleep 3")
|
||||
|
||||
# Now if slapd is down, moulinette tries to restart it
|
||||
mocker.patch("os.system")
|
||||
mocker.patch("time.sleep")
|
||||
with pytest.raises(MoulinetteError) as exception:
|
||||
LDAPAuth().authenticate_credentials(credentials="yunohost")
|
||||
|
||||
translation = m18n.n("ldap_server_down")
|
||||
expected_msg = translation.format()
|
||||
assert expected_msg in str(exception)
|
||||
|
||||
|
||||
def test_authenticate_change_password():
|
||||
|
||||
LDAPAuth().authenticate_credentials(credentials="yunohost")
|
||||
|
||||
tools_adminpw("plopette", check_strength=False)
|
||||
|
||||
with pytest.raises(MoulinetteError) as exception:
|
||||
LDAPAuth().authenticate_credentials(credentials="yunohost")
|
||||
|
||||
translation = m18n.n("invalid_password")
|
||||
expected_msg = translation.format()
|
||||
assert expected_msg in str(exception)
|
||||
|
||||
LDAPAuth().authenticate_credentials(credentials="plopette")
|
|
@ -30,7 +30,7 @@ import time
|
|||
from importlib import import_module
|
||||
from packaging import version
|
||||
|
||||
from moulinette import msignals, m18n
|
||||
from moulinette import Moulinette, m18n
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.process import check_output, call_async_output
|
||||
from moulinette.utils.filesystem import read_yaml, write_to_yaml
|
||||
|
@ -692,7 +692,7 @@ def tools_shutdown(operation_logger, force=False):
|
|||
if not shutdown:
|
||||
try:
|
||||
# Ask confirmation for server shutdown
|
||||
i = msignals.prompt(m18n.n("server_shutdown_confirm", answers="y/N"))
|
||||
i = Moulinette.prompt(m18n.n("server_shutdown_confirm", answers="y/N"))
|
||||
except NotImplemented:
|
||||
pass
|
||||
else:
|
||||
|
@ -711,7 +711,7 @@ def tools_reboot(operation_logger, force=False):
|
|||
if not reboot:
|
||||
try:
|
||||
# Ask confirmation for restoring
|
||||
i = msignals.prompt(m18n.n("server_reboot_confirm", answers="y/N"))
|
||||
i = Moulinette.prompt(m18n.n("server_reboot_confirm", answers="y/N"))
|
||||
except NotImplemented:
|
||||
pass
|
||||
else:
|
||||
|
|
|
@ -33,7 +33,7 @@ import string
|
|||
import subprocess
|
||||
import copy
|
||||
|
||||
from moulinette import msignals, msettings, m18n
|
||||
from moulinette import Moulinette, m18n
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.process import check_output
|
||||
|
||||
|
@ -117,18 +117,18 @@ def user_create(
|
|||
|
||||
# Validate domain used for email address/xmpp account
|
||||
if domain is None:
|
||||
if msettings.get("interface") == "api":
|
||||
if Moulinette.interface.type == "api":
|
||||
raise YunohostValidationError(
|
||||
"Invalid usage, you should specify a domain argument"
|
||||
)
|
||||
else:
|
||||
# On affiche les differents domaines possibles
|
||||
msignals.display(m18n.n("domains_available"))
|
||||
Moulinette.display(m18n.n("domains_available"))
|
||||
for domain in domain_list()["domains"]:
|
||||
msignals.display("- {}".format(domain))
|
||||
Moulinette.display("- {}".format(domain))
|
||||
|
||||
maindomain = _get_maindomain()
|
||||
domain = msignals.prompt(
|
||||
domain = Moulinette.prompt(
|
||||
m18n.n("ask_user_domain") + " (default: %s)" % maindomain
|
||||
)
|
||||
if not domain:
|
||||
|
@ -379,8 +379,8 @@ def user_update(
|
|||
# when in the cli interface if the option to change the password is called
|
||||
# without a specified value, change_password will be set to the const 0.
|
||||
# In this case we prompt for the new password.
|
||||
if msettings.get("interface") == "cli" and not change_password:
|
||||
change_password = msignals.prompt(m18n.n("ask_password"), True, True)
|
||||
if Moulinette.interface.type == "cli" and not change_password:
|
||||
change_password = Moulinette.prompt(m18n.n("ask_password"), True, True)
|
||||
# Ensure sufficiently complex password
|
||||
assert_password_is_strong_enough("user", change_password)
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" License
|
||||
|
||||
Copyright (C) 2019 YunoHost
|
||||
|
@ -21,10 +20,18 @@
|
|||
|
||||
import os
|
||||
import atexit
|
||||
from moulinette.core import MoulinetteLdapIsDownError
|
||||
from moulinette.authenticators import ldap
|
||||
import logging
|
||||
import ldap
|
||||
import ldap.sasl
|
||||
import time
|
||||
import ldap.modlist as modlist
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.utils.error import YunohostError
|
||||
|
||||
logger = logging.getLogger("yunohost.utils.ldap")
|
||||
|
||||
# We use a global variable to do some caching
|
||||
# to avoid re-authenticating in case we call _get_ldap_authenticator multiple times
|
||||
_ldap_interface = None
|
||||
|
@ -35,47 +42,17 @@ def _get_ldap_interface():
|
|||
global _ldap_interface
|
||||
|
||||
if _ldap_interface is None:
|
||||
|
||||
conf = {
|
||||
"vendor": "ldap",
|
||||
"name": "as-root",
|
||||
"parameters": {
|
||||
"uri": "ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi",
|
||||
"base_dn": "dc=yunohost,dc=org",
|
||||
"user_rdn": "gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth",
|
||||
},
|
||||
"extra": {},
|
||||
}
|
||||
|
||||
try:
|
||||
_ldap_interface = ldap.Authenticator(**conf)
|
||||
except MoulinetteLdapIsDownError:
|
||||
raise YunohostError(
|
||||
"Service slapd is not running but is required to perform this action ... You can try to investigate what's happening with 'systemctl status slapd'"
|
||||
)
|
||||
|
||||
assert_slapd_is_running()
|
||||
_ldap_interface = LDAPInterface()
|
||||
|
||||
return _ldap_interface
|
||||
|
||||
|
||||
def assert_slapd_is_running():
|
||||
|
||||
# Assert slapd is running...
|
||||
if not os.system("pgrep slapd >/dev/null") == 0:
|
||||
raise YunohostError(
|
||||
"Service slapd is not running but is required to perform this action ... You can try to investigate what's happening with 'systemctl status slapd'"
|
||||
)
|
||||
|
||||
|
||||
# We regularly want to extract stuff like 'bar' in ldap path like
|
||||
# foo=bar,dn=users.example.org,ou=example.org,dc=org so this small helper allow
|
||||
# to do this without relying of dozens of mysterious string.split()[0]
|
||||
#
|
||||
# e.g. using _ldap_path_extract(path, "foo") on the previous example will
|
||||
# return bar
|
||||
|
||||
|
||||
def _ldap_path_extract(path, info):
|
||||
for element in path.split(","):
|
||||
if element.startswith(info + "="):
|
||||
|
@ -93,3 +70,246 @@ def _destroy_ldap_interface():
|
|||
|
||||
|
||||
atexit.register(_destroy_ldap_interface)
|
||||
|
||||
|
||||
class LDAPInterface:
|
||||
def __init__(self):
|
||||
logger.debug("initializing ldap interface")
|
||||
|
||||
self.uri = "ldapi://%2Fvar%2Frun%2Fslapd%2Fldapi"
|
||||
self.basedn = "dc=yunohost,dc=org"
|
||||
self.rootdn = "gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth"
|
||||
self.connect()
|
||||
|
||||
def connect(self):
|
||||
def _reconnect():
|
||||
con = ldap.ldapobject.ReconnectLDAPObject(
|
||||
self.uri, retry_max=10, retry_delay=0.5
|
||||
)
|
||||
con.sasl_non_interactive_bind_s("EXTERNAL")
|
||||
return con
|
||||
|
||||
try:
|
||||
con = _reconnect()
|
||||
except ldap.SERVER_DOWN:
|
||||
# ldap is down, attempt to restart it before really failing
|
||||
logger.warning(m18n.n("ldap_server_is_down_restart_it"))
|
||||
os.system("systemctl restart slapd")
|
||||
time.sleep(10) # waits 10 secondes so we are sure that slapd has restarted
|
||||
try:
|
||||
con = _reconnect()
|
||||
except ldap.SERVER_DOWN:
|
||||
raise YunohostError(
|
||||
"Service slapd is not running but is required to perform this action ... "
|
||||
"You can try to investigate what's happening with 'systemctl status slapd'"
|
||||
)
|
||||
|
||||
# Check that we are indeed logged in with the right identity
|
||||
try:
|
||||
# whoami_s return dn:..., then delete these 3 characters
|
||||
who = con.whoami_s()[3:]
|
||||
except Exception as e:
|
||||
logger.warning("Error during ldap authentication process: %s", e)
|
||||
raise
|
||||
else:
|
||||
if who != self.rootdn:
|
||||
raise MoulinetteError("Not logged in with the expected userdn ?!")
|
||||
else:
|
||||
self.con = con
|
||||
|
||||
def __del__(self):
|
||||
"""Disconnect and free ressources"""
|
||||
if hasattr(self, "con") and self.con:
|
||||
self.con.unbind_s()
|
||||
|
||||
def search(self, base=None, filter="(objectClass=*)", attrs=["dn"]):
|
||||
"""Search in LDAP base
|
||||
|
||||
Perform an LDAP search operation with given arguments and return
|
||||
results as a list.
|
||||
|
||||
Keyword arguments:
|
||||
- base -- The dn to search into
|
||||
- filter -- A string representation of the filter to apply
|
||||
- attrs -- A list of attributes to fetch
|
||||
|
||||
Returns:
|
||||
A list of all results
|
||||
|
||||
"""
|
||||
if not base:
|
||||
base = self.basedn
|
||||
|
||||
try:
|
||||
result = self.con.search_s(base, ldap.SCOPE_SUBTREE, filter, attrs)
|
||||
except Exception as e:
|
||||
raise MoulinetteError(
|
||||
"error during LDAP search operation with: base='%s', "
|
||||
"filter='%s', attrs=%s and exception %s" % (base, filter, attrs, e),
|
||||
raw_msg=True,
|
||||
)
|
||||
|
||||
result_list = []
|
||||
if not attrs or "dn" not in attrs:
|
||||
result_list = [entry for dn, entry in result]
|
||||
else:
|
||||
for dn, entry in result:
|
||||
entry["dn"] = [dn]
|
||||
result_list.append(entry)
|
||||
|
||||
def decode(value):
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode("utf-8")
|
||||
return value
|
||||
|
||||
# result_list is for example :
|
||||
# [{'virtualdomain': [b'test.com']}, {'virtualdomain': [b'yolo.test']},
|
||||
for stuff in result_list:
|
||||
if isinstance(stuff, dict):
|
||||
for key, values in stuff.items():
|
||||
stuff[key] = [decode(v) for v in values]
|
||||
|
||||
return result_list
|
||||
|
||||
def add(self, rdn, attr_dict):
|
||||
"""
|
||||
Add LDAP entry
|
||||
|
||||
Keyword arguments:
|
||||
rdn -- DN without domain
|
||||
attr_dict -- Dictionnary of attributes/values to add
|
||||
|
||||
Returns:
|
||||
Boolean | MoulinetteError
|
||||
|
||||
"""
|
||||
dn = rdn + "," + self.basedn
|
||||
ldif = modlist.addModlist(attr_dict)
|
||||
for i, (k, v) in enumerate(ldif):
|
||||
if isinstance(v, list):
|
||||
v = [a.encode("utf-8") for a in v]
|
||||
elif isinstance(v, str):
|
||||
v = [v.encode("utf-8")]
|
||||
ldif[i] = (k, v)
|
||||
|
||||
try:
|
||||
self.con.add_s(dn, ldif)
|
||||
except Exception as e:
|
||||
raise MoulinetteError(
|
||||
"error during LDAP add operation with: rdn='%s', "
|
||||
"attr_dict=%s and exception %s" % (rdn, attr_dict, e),
|
||||
raw_msg=True,
|
||||
)
|
||||
else:
|
||||
return True
|
||||
|
||||
def remove(self, rdn):
|
||||
"""
|
||||
Remove LDAP entry
|
||||
|
||||
Keyword arguments:
|
||||
rdn -- DN without domain
|
||||
|
||||
Returns:
|
||||
Boolean | MoulinetteError
|
||||
|
||||
"""
|
||||
dn = rdn + "," + self.basedn
|
||||
try:
|
||||
self.con.delete_s(dn)
|
||||
except Exception as e:
|
||||
raise MoulinetteError(
|
||||
"error during LDAP delete operation with: rdn='%s' and exception %s"
|
||||
% (rdn, e),
|
||||
raw_msg=True,
|
||||
)
|
||||
else:
|
||||
return True
|
||||
|
||||
def update(self, rdn, attr_dict, new_rdn=False):
|
||||
"""
|
||||
Modify LDAP entry
|
||||
|
||||
Keyword arguments:
|
||||
rdn -- DN without domain
|
||||
attr_dict -- Dictionnary of attributes/values to add
|
||||
new_rdn -- New RDN for modification
|
||||
|
||||
Returns:
|
||||
Boolean | MoulinetteError
|
||||
|
||||
"""
|
||||
dn = rdn + "," + self.basedn
|
||||
actual_entry = self.search(base=dn, attrs=None)
|
||||
ldif = modlist.modifyModlist(actual_entry[0], attr_dict, ignore_oldexistent=1)
|
||||
|
||||
if ldif == []:
|
||||
logger.debug("Nothing to update in LDAP")
|
||||
return True
|
||||
|
||||
try:
|
||||
if new_rdn:
|
||||
self.con.rename_s(dn, new_rdn)
|
||||
new_base = dn.split(",", 1)[1]
|
||||
dn = new_rdn + "," + new_base
|
||||
|
||||
for i, (a, k, vs) in enumerate(ldif):
|
||||
if isinstance(vs, list):
|
||||
vs = [v.encode("utf-8") for v in vs]
|
||||
elif isinstance(vs, str):
|
||||
vs = [vs.encode("utf-8")]
|
||||
ldif[i] = (a, k, vs)
|
||||
|
||||
self.con.modify_ext_s(dn, ldif)
|
||||
except Exception as e:
|
||||
raise MoulinetteError(
|
||||
"error during LDAP update operation with: rdn='%s', "
|
||||
"attr_dict=%s, new_rdn=%s and exception: %s"
|
||||
% (rdn, attr_dict, new_rdn, e),
|
||||
raw_msg=True,
|
||||
)
|
||||
else:
|
||||
return True
|
||||
|
||||
def validate_uniqueness(self, value_dict):
|
||||
"""
|
||||
Check uniqueness of values
|
||||
|
||||
Keyword arguments:
|
||||
value_dict -- Dictionnary of attributes/values to check
|
||||
|
||||
Returns:
|
||||
Boolean | MoulinetteError
|
||||
|
||||
"""
|
||||
attr_found = self.get_conflict(value_dict)
|
||||
if attr_found:
|
||||
logger.info(
|
||||
"attribute '%s' with value '%s' is not unique",
|
||||
attr_found[0],
|
||||
attr_found[1],
|
||||
)
|
||||
raise MoulinetteError(
|
||||
"ldap_attribute_already_exists",
|
||||
attribute=attr_found[0],
|
||||
value=attr_found[1],
|
||||
)
|
||||
return True
|
||||
|
||||
def get_conflict(self, value_dict, base_dn=None):
|
||||
"""
|
||||
Check uniqueness of values
|
||||
|
||||
Keyword arguments:
|
||||
value_dict -- Dictionnary of attributes/values to check
|
||||
|
||||
Returns:
|
||||
None | tuple with Fist conflict attribute name and value
|
||||
|
||||
"""
|
||||
for attr, value in value_dict.items():
|
||||
if not self.search(base=base_dn, filter=attr + "=" + value):
|
||||
continue
|
||||
else:
|
||||
return (attr, value)
|
||||
return None
|
||||
|
|
|
@ -2,4 +2,4 @@ import yaml
|
|||
|
||||
|
||||
def test_yaml_syntax():
|
||||
yaml.load(open("data/actionsmap/yunohost.yml"))
|
||||
yaml.safe_load(open("data/actionsmap/yunohost.yml"))
|
||||
|
|
|
@ -35,6 +35,7 @@ def find_expected_string_keys():
|
|||
python_files = glob.glob("src/yunohost/*.py")
|
||||
python_files.extend(glob.glob("src/yunohost/utils/*.py"))
|
||||
python_files.extend(glob.glob("src/yunohost/data_migrations/*.py"))
|
||||
python_files.extend(glob.glob("src/yunohost/authenticators/*.py"))
|
||||
python_files.extend(glob.glob("data/hooks/diagnosis/*.py"))
|
||||
python_files.append("bin/yunohost")
|
||||
|
||||
|
@ -108,7 +109,7 @@ def find_expected_string_keys():
|
|||
yield m
|
||||
|
||||
# Keys for the actionmap ...
|
||||
for category in yaml.load(open("data/actionsmap/yunohost.yml")).values():
|
||||
for category in yaml.safe_load(open("data/actionsmap/yunohost.yml")).values():
|
||||
if "actions" not in category.keys():
|
||||
continue
|
||||
for action in category["actions"].values():
|
||||
|
|
Loading…
Add table
Reference in a new issue