Merge pull request #1502 from YunoHost/11.1

11.1
This commit is contained in:
Alexandre Aubin 2022-10-24 17:58:04 +02:00 committed by GitHub
commit 55baa2aa4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
111 changed files with 5232 additions and 2707 deletions

View file

@ -26,4 +26,4 @@ install-postinstall:
script: script:
- apt-get update -o Acquire::Retries=3 - apt-get update -o Acquire::Retries=3
- DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb - DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb
- yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns --force-diskspace - yunohost tools postinstall -d domain.tld -u syssa -F 'Syssa Mine' -p the_password --ignore-dyndns --force-diskspace

View file

@ -34,7 +34,7 @@ full-tests:
PYTEST_ADDOPTS: "--color=yes" PYTEST_ADDOPTS: "--color=yes"
before_script: before_script:
- *install_debs - *install_debs
- yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns --force-diskspace - yunohost tools postinstall -d domain.tld -u syssa -F 'Syssa Mine' -p the_password --ignore-dyndns --force-diskspace
script: script:
- python3 -m pytest --cov=yunohost tests/ src/tests/ src/diagnosers/ --junitxml=report.xml - python3 -m pytest --cov=yunohost tests/ src/tests/ src/diagnosers/ --junitxml=report.xml
- cd tests - cd tests
@ -125,6 +125,15 @@ test-app-config:
- src/app.py - src/app.py
- src/utils/config.py - src/utils/config.py
test-app-resources:
extends: .test-stage
script:
- python3 -m pytest src/tests/test_app_resources.py
only:
changes:
- src/app.py
- src/utils/resources.py
test-changeurl: test-changeurl:
extends: .test-stage extends: .test-stage
script: script:

View file

@ -1,101 +0,0 @@
YunoHost core contributors
==========================
YunoHost is built and maintained by the YunoHost project community.
Everyone is encouraged to submit issues and changes, and to contribute in other ways -- see https://yunohost.org/contribute to find out how.
--
Initial YunoHost core was built by Kload & beudbeud, for YunoHost v2.
Most of code was written by Kload and jerome, with help of numerous contributors.
Translation is made by a bunch of lovely people all over the world.
We would like to thank anyone who ever helped the YunoHost project <3
YunoHost core Contributors
--------------------------
- Jérôme Lebleu
- Kload
- Laurent 'Bram' Peuch
- Julien 'ju' Malik
- opi
- Aleks
- Adrien 'beudbeud' Beudin
- M5oul
- Valentin 'zamentur' / 'ljf' Grimaud
- Jocelyn Delalande
- infertux
- Taziden
- ZeHiro
- Josue-T
- nahoj
- a1ex
- JimboJoe
- vetetix
- jellium
- Sebastien 'sebian' Badia
- lmangani
- Julien Vaubourg
- thardev
- zimo2001
YunoHost core Translators
-------------------------
If you want to help translation, please visit https://translate.yunohost.org/projects/yunohost/yunohost/
### Dutch
- DUBWiSE
- Jeroen Keerl
- marut
### English
- Bugsbane
- rokaz
### French
- aoz roon
- Genma
- Jean-Baptiste Holcroft
- Jean P.
- Jérôme Lebleu
- Lapineige
- paddy
### German
- david.bartke
- Fabian Gruber
- Felix Bartels
- Jeroen Keerl
- martin kistner
- Philip Gatzka
### Hindi
- Anmol
### Italian
- bricabrac
- Thomas Bille
### Portuguese
- Deleted User
- Trollken
### Spanish
- Juanu

View file

@ -1,5 +1,4 @@
#! /usr/bin/python3 #! /usr/bin/python3
# -*- coding: utf-8 -*-
import os import os
import sys import sys

View file

@ -1,5 +1,4 @@
#! /usr/bin/python3 #! /usr/bin/python3
# -*- coding: utf-8 -*-
import argparse import argparse
import yunohost import yunohost

View file

@ -44,10 +44,10 @@ server {
ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem; ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem;
ssl_certificate_key /etc/yunohost/certs/{{ domain }}/key.pem; ssl_certificate_key /etc/yunohost/certs/{{ domain }}/key.pem;
{% if domain_cert_ca != "Self-signed" %} {% if domain_cert_ca != "selfsigned" %}
more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload";
{% endif %} {% endif %}
{% if domain_cert_ca == "Let's Encrypt" %} {% if domain_cert_ca == "letsencrypt" %}
# OCSP settings # OCSP settings
ssl_stapling on; ssl_stapling on;
ssl_stapling_verify on; ssl_stapling_verify on;
@ -99,10 +99,10 @@ server {
ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem; ssl_certificate /etc/yunohost/certs/{{ domain }}/crt.pem;
ssl_certificate_key /etc/yunohost/certs/{{ domain }}/key.pem; ssl_certificate_key /etc/yunohost/certs/{{ domain }}/key.pem;
{% if domain_cert_ca != "Self-signed" %} {% if domain_cert_ca != "selfsigned" %}
more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload"; more_set_headers "Strict-Transport-Security : max-age=63072000; includeSubDomains; preload";
{% endif %} {% endif %}
{% if domain_cert_ca == "Let's Encrypt" %} {% if domain_cert_ca == "letsencrypt" %}
# OCSP settings # OCSP settings
ssl_stapling on; ssl_stapling on;
ssl_stapling_verify on; ssl_stapling_verify on;

View file

@ -81,7 +81,7 @@ alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases alias_database = hash:/etc/aliases
mydomain = {{ main_domain }} mydomain = {{ main_domain }}
mydestination = localhost mydestination = localhost
{% if relay_host == "" %} {% if relay_enabled != "True" %}
relayhost = relayhost =
{% else %} {% else %}
relayhost = [{{ relay_host }}]:{{ relay_port }} relayhost = [{{ relay_host }}]:{{ relay_port }}
@ -102,7 +102,7 @@ message_size_limit = 35914708
virtual_mailbox_domains = ldap:/etc/postfix/ldap-domains.cf virtual_mailbox_domains = ldap:/etc/postfix/ldap-domains.cf
virtual_mailbox_maps = ldap:/etc/postfix/ldap-accounts.cf virtual_mailbox_maps = ldap:/etc/postfix/ldap-accounts.cf
virtual_mailbox_base = virtual_mailbox_base =
virtual_alias_maps = ldap:/etc/postfix/ldap-aliases.cf virtual_alias_maps = ldap:/etc/postfix/ldap-aliases.cf,ldap:/etc/postfix/ldap-groups.cf
virtual_alias_domains = virtual_alias_domains =
virtual_minimum_uid = 100 virtual_minimum_uid = 100
virtual_uid_maps = static:vmail virtual_uid_maps = static:vmail
@ -198,7 +198,7 @@ smtpd_client_recipient_rate_limit=150
# and after to send spam # and after to send spam
disable_vrfy_command = yes disable_vrfy_command = yes
{% if relay_user != "" %} {% if relay_enabled == "True" %}
# Relay email through an other smtp account # Relay email through an other smtp account
# enable SASL authentication # enable SASL authentication
smtp_sasl_auth_enable = yes smtp_sasl_auth_enable = yes

View file

@ -0,0 +1,9 @@
server_host = localhost
server_port = 389
search_base = dc=yunohost,dc=org
query_filter = (&(objectClass=groupOfNamesYnh)(mail=%s))
exclude_internal = yes
search_timeout = 30
scope = sub
result_attribute = memberUid, mail
terminal_result_attribute = memberUid

View file

@ -130,7 +130,6 @@ olcSuffix: dc=yunohost,dc=org
# admin entry below # admin entry below
# These access lines apply to database #1 only # These access lines apply to database #1 only
olcAccess: {0}to attrs=userPassword,shadowLastChange olcAccess: {0}to attrs=userPassword,shadowLastChange
by dn.base="cn=admin,dc=yunohost,dc=org" write
by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
by anonymous auth by anonymous auth
by self write by self write
@ -140,7 +139,6 @@ olcAccess: {0}to attrs=userPassword,shadowLastChange
# owning it if they are authenticated. # owning it if they are authenticated.
# Others should be able to see it. # Others should be able to see it.
olcAccess: {1}to attrs=cn,gecos,givenName,mail,maildrop,displayName,sn olcAccess: {1}to attrs=cn,gecos,givenName,mail,maildrop,displayName,sn
by dn.base="cn=admin,dc=yunohost,dc=org" write
by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
by self write by self write
by * read by * read
@ -160,9 +158,7 @@ olcAccess: {2}to dn.base=""
# The admin dn has full write access, everyone else # The admin dn has full write access, everyone else
# can read everything. # can read everything.
olcAccess: {3}to * olcAccess: {3}to *
by dn.base="cn=admin,dc=yunohost,dc=org" write
by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
by group/groupOfNames/member.exact="cn=admin,ou=groups,dc=yunohost,dc=org" write
by * read by * read
# #
olcAddContentAcl: FALSE olcAddContentAcl: FALSE

View file

@ -5,15 +5,6 @@ objectClass: organization
o: yunohost.org o: yunohost.org
dc: yunohost dc: yunohost
dn: cn=admin,ou=sudo,dc=yunohost,dc=org
cn: admin
objectClass: sudoRole
objectClass: top
sudoCommand: ALL
sudoUser: admin
sudoOption: !authenticate
sudoHost: ALL
dn: ou=users,dc=yunohost,dc=org dn: ou=users,dc=yunohost,dc=org
objectClass: organizationalUnit objectClass: organizationalUnit
objectClass: top objectClass: top
@ -39,28 +30,31 @@ objectClass: organizationalUnit
objectClass: top objectClass: top
ou: groups ou: groups
dn: cn=admins,ou=sudo,dc=yunohost,dc=org
cn: admins
objectClass: sudoRole
objectClass: top
sudoCommand: ALL
sudoUser: %admins
sudoHost: ALL
dn: ou=sudo,dc=yunohost,dc=org dn: ou=sudo,dc=yunohost,dc=org
objectClass: organizationalUnit objectClass: organizationalUnit
objectClass: top objectClass: top
ou: sudo ou: sudo
dn: cn=admin,dc=yunohost,dc=org
objectClass: organizationalRole
objectClass: posixAccount
objectClass: simpleSecurityObject
cn: admin
uid: admin
uidNumber: 1007
gidNumber: 1007
homeDirectory: /home/admin
loginShell: /bin/bash
userPassword: yunohost
dn: cn=admins,ou=groups,dc=yunohost,dc=org dn: cn=admins,ou=groups,dc=yunohost,dc=org
objectClass: posixGroup objectClass: posixGroup
objectClass: top objectClass: top
memberUid: admin objectClass: groupOfNamesYnh
objectClass: mailGroup
gidNumber: 4001 gidNumber: 4001
mail: root
mail: admin
mail: admins
mail: webmaster
mail: postmaster
mail: abuse
cn: admins cn: admins
dn: cn=all_users,ou=groups,dc=yunohost,dc=org dn: cn=all_users,ou=groups,dc=yunohost,dc=org

View file

@ -89,4 +89,7 @@ olcObjectClasses: ( 1.3.6.1.4.1.40328.1.1.2.3
NAME 'mailGroup' SUP top AUXILIARY NAME 'mailGroup' SUP top AUXILIARY
DESC 'Mail Group' DESC 'Mail Group'
MUST ( mail ) MUST ( mail )
MAY (
mailalias $ maildrop
)
) )

View file

@ -3,7 +3,7 @@
Protocol 2 Protocol 2
# PLEASE: if you wish to change the ssh port properly in YunoHost, use this command: # PLEASE: if you wish to change the ssh port properly in YunoHost, use this command:
# yunohost settings set security.ssh.port -v <port> # yunohost settings set security.ssh.ssh_port -v <port>
Port {{ port }} Port {{ port }}
{% if ipv6_enabled == "true" %}ListenAddress ::{% endif %} {% if ipv6_enabled == "true" %}ListenAddress ::{% endif %}
@ -56,7 +56,7 @@ ChallengeResponseAuthentication no
UsePAM yes UsePAM yes
# PLEASE: if you wish to force everybody to authenticate using ssh keys, run this command: # PLEASE: if you wish to force everybody to authenticate using ssh keys, run this command:
# yunohost settings set security.ssh.password_authentication -v no # yunohost settings set security.ssh.ssh_password_authentication -v no
{% if password_authentication == "False" %} {% if password_authentication == "False" %}
PasswordAuthentication no PasswordAuthentication no
{% else %} {% else %}

View file

@ -0,0 +1,12 @@
from yunohost.utils.resources import AppResourceClassesByType
resources = sorted(AppResourceClassesByType.values(), key=lambda r: r.priority)
for klass in resources:
doc = klass.__doc__.replace("\n ", "\n")
print("")
print(f"## {klass.type.replace('_', ' ').title()}")
print("")
print(doc)

View file

@ -227,9 +227,8 @@ ynh_install_app_dependencies() {
# Add a comma for each space between packages. But not add a comma if the space separate a version specification. (See below) # Add a comma for each space between packages. But not add a comma if the space separate a version specification. (See below)
dependencies="$(echo "$dependencies" | sed 's/\([^\<=\>]\)\ \([^(]\)/\1, \2/g')" dependencies="$(echo "$dependencies" | sed 's/\([^\<=\>]\)\ \([^(]\)/\1, \2/g')"
local dependencies=${dependencies//|/ | } local dependencies=${dependencies//|/ | }
local manifest_path="$YNH_APP_BASEDIR/manifest.json"
local version=$(jq -r '.version' "$manifest_path") local version=$(ynh_read_manifest --manifest_key="version")
if [ -z "${version}" ] || [ "$version" == "null" ]; then if [ -z "${version}" ] || [ "$version" == "null" ]; then
version="1.0" version="1.0"
fi fi

View file

@ -285,6 +285,18 @@ ynh_app_config_apply() {
_ynh_app_config_apply _ynh_app_config_apply
} }
ynh_app_action_run() {
local runner="run__$1"
# Get value from getter if exists
if type -t "$runner" 2>/dev/null | grep -q '^function$' 2>/dev/null; then
$runner
#ynh_return "result:"
#ynh_return "$(echo "${result}" | sed 's/^/ /g')"
else
ynh_die "No handler defined in app's script for action $1. If you are the maintainer of this app, you should define '$runner'"
fi
}
ynh_app_config_run() { ynh_app_config_run() {
declare -Ag old=() declare -Ag old=()
declare -Ag changed=() declare -Ag changed=()
@ -309,5 +321,7 @@ ynh_app_config_run() {
ynh_app_config_apply ynh_app_config_apply
ynh_script_progression --message="Configuration of $app completed" --last ynh_script_progression --message="Configuration of $app completed" --last
;; ;;
*)
ynh_app_action_run $1
esac esac
} }

View file

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
n_version=8.2.0 n_version=9.0.0
n_checksum=75efd9e583836f3e6cc6d793df1501462fdceeb3460d5a2dbba99993997383b9 n_checksum=37a987230d1ed0392a83f9c02c1e535a524977c00c64a4adb771ab60237be1c6
n_install_dir="/opt/node_n" n_install_dir="/opt/node_n"
node_version_path="$n_install_dir/n/versions/node" node_version_path="$n_install_dir/n/versions/node"
# N_PREFIX is the directory of n, it needs to be loaded as a environment variable. # N_PREFIX is the directory of n, it needs to be loaded as a environment variable.

View file

@ -61,6 +61,12 @@ ynh_abort_if_errors() {
trap ynh_exit_properly EXIT # Capturing exit signals on shell script trap ynh_exit_properly EXIT # Capturing exit signals on shell script
} }
# When running an app script with packaging format >= 2, auto-enable ynh_abort_if_errors except for remove script
if dpkg --compare-versions ${YNH_APP_PACKAGING_FORMAT:-0} ge 2 && [[ ${YNH_APP_ACTION} != "remove" ]]
then
ynh_abort_if_errors
fi
# Download, check integrity, uncompress and patch the source from app.src # Download, check integrity, uncompress and patch the source from app.src
# #
# usage: ynh_setup_source --dest_dir=dest_dir [--source_id=source_id] [--keep="file1 file2"] [--full_replace] # usage: ynh_setup_source --dest_dir=dest_dir [--source_id=source_id] [--keep="file1 file2"] [--full_replace]
@ -87,6 +93,8 @@ ynh_abort_if_errors() {
# # (Optional) If it set as false don't extract the source. Default: true # # (Optional) If it set as false don't extract the source. Default: true
# # (Useful to get a debian package or a python wheel.) # # (Useful to get a debian package or a python wheel.)
# SOURCE_EXTRACT=(true|false) # SOURCE_EXTRACT=(true|false)
# # (Optionnal) Name of the plateform. Default: "linux/$YNH_ARCH"
# SOURCE_PLATFORM=linux/arm64/v8
# ``` # ```
# #
# The helper will: # The helper will:
@ -122,9 +130,10 @@ ynh_setup_source() {
local src_sum=$(grep 'SOURCE_SUM=' "$src_file_path" | cut --delimiter='=' --fields=2-) local src_sum=$(grep 'SOURCE_SUM=' "$src_file_path" | cut --delimiter='=' --fields=2-)
local src_sumprg=$(grep 'SOURCE_SUM_PRG=' "$src_file_path" | cut --delimiter='=' --fields=2-) local src_sumprg=$(grep 'SOURCE_SUM_PRG=' "$src_file_path" | cut --delimiter='=' --fields=2-)
local src_format=$(grep 'SOURCE_FORMAT=' "$src_file_path" | cut --delimiter='=' --fields=2-) local src_format=$(grep 'SOURCE_FORMAT=' "$src_file_path" | cut --delimiter='=' --fields=2-)
local src_extract=$(grep 'SOURCE_EXTRACT=' "$src_file_path" | cut --delimiter='=' --fields=2-)
local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$src_file_path" | cut --delimiter='=' --fields=2-) local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$src_file_path" | cut --delimiter='=' --fields=2-)
local src_filename=$(grep 'SOURCE_FILENAME=' "$src_file_path" | cut --delimiter='=' --fields=2-) local src_filename=$(grep 'SOURCE_FILENAME=' "$src_file_path" | cut --delimiter='=' --fields=2-)
local src_extract=$(grep 'SOURCE_EXTRACT=' "$src_file_path" | cut --delimiter='=' --fields=2-)
local src_plateform=$(grep 'SOURCE_PLATFORM=' "$src_file_path" | cut --delimiter='=' --fields=2-)
# Default value # Default value
src_sumprg=${src_sumprg:-sha256sum} src_sumprg=${src_sumprg:-sha256sum}
@ -142,7 +151,9 @@ ynh_setup_source() {
mkdir -p /var/cache/yunohost/download/${YNH_APP_ID}/ mkdir -p /var/cache/yunohost/download/${YNH_APP_ID}/
src_filename="/var/cache/yunohost/download/${YNH_APP_ID}/${src_filename}" src_filename="/var/cache/yunohost/download/${YNH_APP_ID}/${src_filename}"
if test -e "$local_src"; then if [ "$src_format" = "docker" ]; then
src_plateform="${src_plateform:-"linux/$YNH_ARCH"}"
elif test -e "$local_src"; then
cp $local_src $src_filename cp $local_src $src_filename
else else
[ -n "$src_url" ] || ynh_die "Couldn't parse SOURCE_URL from $src_file_path ?" [ -n "$src_url" ] || ynh_die "Couldn't parse SOURCE_URL from $src_file_path ?"
@ -154,12 +165,11 @@ ynh_setup_source() {
# Timeout option is here to enforce the timeout on dns query and tcp connect (c.f. man wget) # Timeout option is here to enforce the timeout on dns query and tcp connect (c.f. man wget)
out=$(wget --tries 3 --no-dns-cache --timeout 900 --no-verbose --output-document=$src_filename $src_url 2>&1) \ out=$(wget --tries 3 --no-dns-cache --timeout 900 --no-verbose --output-document=$src_filename $src_url 2>&1) \
|| ynh_die --message="$out" || ynh_die --message="$out"
# Check the control sum
echo "${src_sum} ${src_filename}" | ${src_sumprg} --check --status \
|| ynh_die --message="Corrupt source"
fi fi
# Check the control sum
echo "${src_sum} ${src_filename}" | ${src_sumprg} --check --status \
|| ynh_die --message="Corrupt source"
# Keep files to be backup/restored at the end of the helper # Keep files to be backup/restored at the end of the helper
# Assuming $dest_dir already exists # Assuming $dest_dir already exists
rm -rf /var/cache/yunohost/files_to_keep_during_setup_source/ rm -rf /var/cache/yunohost/files_to_keep_during_setup_source/
@ -188,6 +198,8 @@ ynh_setup_source() {
if ! "$src_extract"; then if ! "$src_extract"; then
mv $src_filename $dest_dir mv $src_filename $dest_dir
elif [ "$src_format" = "docker" ]; then
/usr/share/yunohost/helpers.d/vendor/docker-image-extract/docker-image-extract -p $src_plateform -o $dest_dir $src_url 2>&1
elif [ "$src_format" = "zip" ]; then elif [ "$src_format" = "zip" ]; then
# Zip format # Zip format
# Using of a temp directory, because unzip doesn't manage --strip-components # Using of a temp directory, because unzip doesn't manage --strip-components
@ -765,12 +777,25 @@ ynh_read_manifest() {
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
if [ ! -e "$manifest" ]; then if [ ! -e "${manifest:-}" ]; then
# If the manifest isn't found, try the common place for backup and restore script. # If the manifest isn't found, try the common place for backup and restore script.
manifest="$YNH_APP_BASEDIR/manifest.json" if [ -e "$YNH_APP_BASEDIR/manifest.json" ]
then
manifest="$YNH_APP_BASEDIR/manifest.json"
elif [ -e "$YNH_APP_BASEDIR/manifest.toml" ]
then
manifest="$YNH_APP_BASEDIR/manifest.toml"
else
ynh_die --message "No manifest found !?"
fi
fi fi
jq ".$manifest_key" "$manifest" --raw-output if echo "$manifest" | grep -q '\.json$'
then
jq ".$manifest_key" "$manifest" --raw-output
else
cat "$manifest" | python3 -c 'import json, toml, sys; print(json.dumps(toml.load(sys.stdin)))' | jq ".$manifest_key" --raw-output
fi
} }
# Read the upstream version from the manifest or `$YNH_APP_MANIFEST_VERSION` # Read the upstream version from the manifest or `$YNH_APP_MANIFEST_VERSION`
@ -914,9 +939,9 @@ ynh_compare_current_package_version() {
_ynh_apply_default_permissions() { _ynh_apply_default_permissions() {
local target=$1 local target=$1
local ynh_requirement=$(jq -r '.requirements.yunohost' $YNH_APP_BASEDIR/manifest.json | tr -d '>= ') local ynh_requirement=$(ynh_read_manifest --manifest_key="requirements.yunohost" | tr -d '<>= ')
if [ -z "$ynh_requirement" ] || [ "$ynh_requirement" == "null" ] || dpkg --compare-versions $ynh_requirement ge 4.2; then if dpkg --compare-versions ${YNH_APP_PACKAGING_FORMAT:-0} ge 2 || [ -z "$ynh_requirement" ] || [ "$ynh_requirement" == "null" ] || dpkg --compare-versions $ynh_requirement ge 4.2; then
chmod o-rwx $target chmod o-rwx $target
chmod g-w $target chmod g-w $target
chown -R root:root $target chown -R root:root $target

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Emmanuel Frecon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1 @@
This is taken from https://github.com/efrecon/docker-image-extract

215
helpers/vendor/docker-image-extract/extract.sh vendored Executable file
View file

@ -0,0 +1,215 @@
#!/bin/sh
# If editing from Windows. Choose LF as line-ending
set -eu
# Set this to 1 for more verbosity (on stderr)
EXTRACT_VERBOSE=${EXTRACT_VERBOSE:-0}
# Destination directory, some %-surrounded keywords will be dynamically replaced
# by elements of the fully-qualified image name.
EXTRACT_DEST=${EXTRACT_DEST:-"$(pwd)"}
# Pull if the image does not exist. If the image had to be pulled, it will
# automatically be removed once done to conserve space.
EXTRACT_PULL=${EXTRACT_PULL:-1}
# Docker client command to use
EXTRACT_DOCKER=${EXTRACT_DOCKER:-"docker"}
# Export PATHs to binaries and libraries
EXTRACT_EXPORT=${EXTRACT_EXPORT:-0}
# Name of manifest file containing the description of the layers
EXTRACT_MANIFEST=${EXTRACT_MANIFEST:-"manifest.json"}
# This uses the comments behind the options to show the help. Not extremly
# correct, but effective and simple.
usage() {
echo "$0 extracts all layers from a Docker image to a directory, will pull if necessary" && \
grep "[[:space:]].)\ #" "$0" |
sed 's/#//' |
sed -r 's/([a-z])\)/-\1/'
exit "${1:-0}"
}
while getopts "t:d:vneh-" opt; do
case "$opt" in
d) # How to run the Docker client
EXTRACT_DOCKER=$OPTARG;;
e) # Print out commands for PATH extraction
EXTRACT_EXPORT=1;;
n) # Do not pull if the image does not exist
EXTRACT_PULL=0;;
h) # Print help and exit
usage;;
t) # Target directory, will be created if necessary, %-surrounded keywords will be resolved (see manual). Default: current directory
EXTRACT_DEST=$OPTARG;;
v) # Turn on verbosity
EXTRACT_VERBOSE=1;;
-)
break;;
*)
usage 1;;
esac
done
shift $((OPTIND-1))
_verbose() {
if [ "$EXTRACT_VERBOSE" = "1" ]; then
printf %s\\n "$1" >&2
fi
}
_error() {
printf %s\\n "$1" >&2
}
# This will unfold JSON onliners to arrange for having fields and their values
# on separated lines. It's sed and grep, don't expect miracles, but this should
# work against most well-formatted JSON.
json_unfold() {
sed -E \
-e 's/\}\s*,\s*\{/\n\},\n\{\n/g' \
-e 's/\{\s*"/\{\n"/g' \
-e 's/(.+)\}/\1\n\}/g' \
-e 's/"\s*:\s*(("[^"]+")|([a-zA-Z0-9]+))\s*([,$])/": \1\4\n/g' \
-e 's/"\s*:\s*(("[^"]+")|([a-zA-Z0-9]+))\s*\}/": \1\n\}/g' | \
grep -vEe '^\s*$'
}
extract() {
# Extract details out of image name
fullname=$1
tag=""
if printf %s\\n "$1"|grep -Eq '@sha256:[a-f0-9A-F]{64}$'; then
tag=$(printf %s\\n "$1"|grep -Eo 'sha256:[a-f0-9A-F]{64}$')
fullname=$(printf %s\\n "$1"|sed -E 's/(.*)@sha256:[a-f0-9A-F]{64}$/\1/')
elif printf %s\\n "$1"|grep -Eq ':[[:alnum:]_][[:alnum:]_.-]{0,127}$'; then
tag=$(printf %s\\n "$1"|grep -Eo ':[[:alnum:]_][[:alnum:]_.-]{0,127}$'|cut -c 2-)
fullname=$(printf %s\\n "$1"|sed -E 's/(.*):[[:alnum:]_][[:alnum:]_.-]{0,127}$/\1/')
fi
shortname=$(printf %s\\n "$fullname" | awk -F / '{printf $NF}')
fullname_flat=$(printf %s\\n "$fullname" | sed 's~/~_~g')
if [ -z "$tag" ]; then
fullyqualified_flat=$(printf %s_%s\\n "$fullname_flat" "latest")
else
fullyqualified_flat=$(printf %s_%s\\n "$fullname_flat" "$tag")
fi
# Generate the name of the destination directory, replacing the
# sugared-strings by their values. We use the ~ character as a separator in
# the sed expressions as / might appear in the values.
dst=$(printf %s\\n "$EXTRACT_DEST" |
sed -E \
-e "s~%tag%~${tag}~" \
-e "s~%fullname%~${fullname}~" \
-e "s~%shortname%~${shortname}~" \
-e "s~%fullname_flat%~${fullname_flat}~" \
-e "s~%fullyqualified_flat%~${fullyqualified_flat}~" \
-e "s~%name%~${1}~" \
)
# Pull image on demand, if necessary and when EXTRACT_PULL was set to 1
imgrm=0
if ! ${EXTRACT_DOCKER} image inspect "$1" >/dev/null 2>&1 && [ "$EXTRACT_PULL" = "1" ]; then
_verbose "Pulling image '$1', will remove it upon completion"
${EXTRACT_DOCKER} image pull "$1"
imgrm=1
fi
if ${EXTRACT_DOCKER} image inspect "$1" >/dev/null 2>&1 ; then
# Create a temporary directory to store the content of the image itself, i.e.
# the result of docker image save on the image.
TMPD=$(mktemp -t -d image-XXXXX)
# Extract image to the temporary directory
_verbose "Extracting content of '$1' to temporary storage"
${EXTRACT_DOCKER} image save "$1" | tar -C "$TMPD" -xf -
# Create destination directory, if necessary
if ! [ -d "$dst" ]; then
_verbose "Creating destination directory: '$dst' (resolved from '$EXTRACT_DEST')"
mkdir -p "$dst"
fi
# Extract all layers of the image, in the order specified by the manifest,
# into the destination directory.
if [ -f "${TMPD}/${EXTRACT_MANIFEST}" ]; then
json_unfold < "${TMPD}/${EXTRACT_MANIFEST}" |
grep -oE '[a-fA-F0-9]{64}/[[:alnum:]]+\.tar' |
while IFS= read -r layer; do
_verbose "Extracting layer $(printf %s\\n "$layer" | awk -F '/' '{print $1}')"
tar -C "$dst" -xf "${TMPD}/${layer}"
done
else
_error "Cannot find $EXTRACT_MANIFEST in image content!"
fi
# Remove temporary content of image save.
rm -rf "$TMPD"
if [ "$EXTRACT_EXPORT" = "1" ]; then
# Resolve destination directory to absolute path
rdst=$(cd -P -- "$dst" && pwd -P)
for top in "" /usr /usr/local; do
# Add binaries
for sub in /sbin /bin; do
bdir=${rdst%/}${top%/}${sub}
if [ -d "$bdir" ] \
&& [ "$(find "$bdir" -maxdepth 1 -mindepth 1 -type f -executable | wc -l)" -gt "0" ]; then
if [ -z "${GITHUB_PATH+x}" ]; then
BPATH="${bdir}:${BPATH}"
else
printf %s\\n "$bdir" >> "$GITHUB_PATH"
fi
fi
done
# Add libraries
for sub in /lib; do
ldir=${rdst%/}${top%/}${sub}
if [ -d "$ldir" ] \
&& [ "$(find "$ldir" -maxdepth 1 -mindepth 1 -type f -executable -name '*.so*'| wc -l)" -gt "0" ]; then
LPATH="${ldir}:${LPATH}"
fi
done
done
fi
else
_error "Image $1 not present at Docker daemon"
fi
if [ "$imgrm" = "1" ]; then
_verbose "Removing image $1 from host"
${EXTRACT_DOCKER} image rm "$1"
fi
}
# We need at least one image
if [ "$#" = "0" ]; then
usage
fi
# Extract all images, one by one, to the target directory
BPATH=$(printf %s\\n "$PATH" | sed 's/ /\\ /g')
LPATH=$(printf %s\\n "${LD_LIBRARY_PATH:-}" | sed 's/ /\\ /g')
for i in "$@"; do
extract "$i"
done
if [ "$EXTRACT_EXPORT" = "1" ]; then
if [ -z "${GITHUB_PATH+x}" ]; then
printf "PATH=\"%s\"\n" "$BPATH"
if [ -n "$LPATH" ]; then
printf "LD_LIBRARY_PATH=\"%s\"\n" "$LPATH"
fi
elif [ -n "$LPATH" ]; then
printf "LD_LIBRARY_PATH=\"%s\"\n" "$LPATH" >> "$GITHUB_ENV"
fi
fi

View file

@ -13,6 +13,6 @@ backup_dir="${1}/conf/ynh"
ynh_backup "/etc/yunohost/firewall.yml" "${backup_dir}/firewall.yml" ynh_backup "/etc/yunohost/firewall.yml" "${backup_dir}/firewall.yml"
ynh_backup "/etc/yunohost/current_host" "${backup_dir}/current_host" ynh_backup "/etc/yunohost/current_host" "${backup_dir}/current_host"
[ ! -d "/etc/yunohost/domains" ] || ynh_backup "/etc/yunohost/domains" "${backup_dir}/domains" [ ! -d "/etc/yunohost/domains" ] || ynh_backup "/etc/yunohost/domains" "${backup_dir}/domains"
[ ! -e "/etc/yunohost/settings.json" ] || ynh_backup "/etc/yunohost/settings.json" "${backup_dir}/settings.json" [ ! -e "/etc/yunohost/settings.yml" ] || ynh_backup "/etc/yunohost/settings.yml" "${backup_dir}/settings.yml"
[ ! -d "/etc/yunohost/dyndns" ] || ynh_backup "/etc/yunohost/dyndns" "${backup_dir}/dyndns" [ ! -d "/etc/yunohost/dyndns" ] || ynh_backup "/etc/yunohost/dyndns" "${backup_dir}/dyndns"
[ ! -d "/etc/dkim" ] || ynh_backup "/etc/dkim" "${backup_dir}/dkim" [ ! -d "/etc/dkim" ] || ynh_backup "/etc/dkim" "${backup_dir}/dkim"

View file

@ -42,7 +42,7 @@ do_init_regen() {
# Backup folders # Backup folders
mkdir -p /home/yunohost.backup/archives mkdir -p /home/yunohost.backup/archives
chmod 750 /home/yunohost.backup/archives chmod 750 /home/yunohost.backup/archives
chown root:root /home/yunohost.backup/archives # This is later changed to admin:root once admin user exists chown root:root /home/yunohost.backup/archives # This is later changed to root:admins once the admins group exists
# Empty ssowat json persistent conf # Empty ssowat json persistent conf
echo "{}" >'/etc/ssowat/conf.json.persistent' echo "{}" >'/etc/ssowat/conf.json.persistent'
@ -168,12 +168,11 @@ do_post_regen() {
# Enfore permissions # # Enfore permissions #
###################### ######################
chmod 750 /home/admin chmod 770 /home/yunohost.backup
chmod 750 /home/yunohost.backup chmod 770 /home/yunohost.backup/archives
chmod 750 /home/yunohost.backup/archives
chmod 700 /var/cache/yunohost chmod 700 /var/cache/yunohost
chown admin:root /home/yunohost.backup chown root:admins /home/yunohost.backup
chown admin:root /home/yunohost.backup/archives chown root:admins /home/yunohost.backup/archives
chown root:root /var/cache/yunohost chown root:root /var/cache/yunohost
# NB: x permission for 'others' is important for ssl-cert (and maybe mdns), otherwise slapd will fail to start because can't access the certs # NB: x permission for 'others' is important for ssl-cert (and maybe mdns), otherwise slapd will fail to start because can't access the certs

View file

@ -14,15 +14,10 @@ do_pre_regen() {
ssh_keys=$(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key 2>/dev/null || true) ssh_keys=$(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key 2>/dev/null || true)
# Support legacy setting (this setting might be disabled by a user during a migration)
if [[ "$(yunohost settings get 'service.ssh.allow_deprecated_dsa_hostkey')" == "True" ]]; then
ssh_keys="$ssh_keys $(ls /etc/ssh/ssh_host_dsa_key 2>/dev/null || true)"
fi
# Support different strategy for security configurations # Support different strategy for security configurations
export compatibility="$(yunohost settings get 'security.ssh.compatibility')" export compatibility="$(yunohost settings get 'security.ssh.ssh_compatibility')"
export port="$(yunohost settings get 'security.ssh.port')" export port="$(yunohost settings get 'security.ssh.ssh_port')"
export password_authentication="$(yunohost settings get 'security.ssh.password_authentication')" export password_authentication="$(yunohost settings get 'security.ssh.ssh_password_authentication')"
export ssh_keys export ssh_keys
export ipv6_enabled export ipv6_enabled
ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config" ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config"

View file

@ -58,14 +58,6 @@ EOF
nscd -i passwd || true nscd -i passwd || true
systemctl restart slapd systemctl restart slapd
# We don't use mkhomedir_helper because 'admin' may not be recognized
# when this script is ran in a chroot (e.g. ISO install)
# We also refer to admin as uid 1007 for the same reason
if [ ! -d /home/admin ]; then
cp -r /etc/skel /home/admin
chown -R 1007:1007 /home/admin
fi
} }
_regenerate_slapd_conf() { _regenerate_slapd_conf() {
@ -172,22 +164,6 @@ objectClass: top"
echo "Reloading slapd" echo "Reloading slapd"
systemctl force-reload slapd systemctl force-reload slapd
# on slow hardware/vm this regen conf would exit before the admin user that
# is stored in ldap is available because ldap seems to slow to restart
# so we'll wait either until we are able to log as admin or until a timeout
# is reached
# we need to do this because the next hooks executed after this one during
# postinstall requires to run as admin thus breaking postinstall on slow
# hardware which mean yunohost can't be correctly installed on those hardware
# and this sucks
# wait a maximum time of 5 minutes
# yes, force-reload behave like a restart
number_of_wait=0
while ! su admin -c '' && ((number_of_wait < 60)); do
sleep 5
((number_of_wait += 1))
done
} }
do_$1_regen ${@:2} do_$1_regen ${@:2}

View file

@ -56,7 +56,7 @@ do_pre_regen() {
# install / update plain conf files # install / update plain conf files
cp plain/* "$nginx_conf_dir" cp plain/* "$nginx_conf_dir"
# remove the panel overlay if this is specified in settings # remove the panel overlay if this is specified in settings
panel_overlay=$(yunohost settings get 'ssowat.panel_overlay.enabled') panel_overlay=$(yunohost settings get 'misc.portal.ssowat_panel_overlay_enabled')
if [ "$panel_overlay" == "false" ] || [ "$panel_overlay" == "False" ]; then if [ "$panel_overlay" == "false" ] || [ "$panel_overlay" == "False" ]; then
echo "#" >"${nginx_conf_dir}/yunohost_panel.conf.inc" echo "#" >"${nginx_conf_dir}/yunohost_panel.conf.inc"
fi fi
@ -65,9 +65,9 @@ do_pre_regen() {
main_domain=$(cat /etc/yunohost/current_host) main_domain=$(cat /etc/yunohost/current_host)
# Support different strategy for security configurations # Support different strategy for security configurations
export redirect_to_https="$(yunohost settings get 'security.nginx.redirect_to_https')" export redirect_to_https="$(yunohost settings get 'security.nginx.nginx_redirect_to_https')"
export compatibility="$(yunohost settings get 'security.nginx.compatibility')" export compatibility="$(yunohost settings get 'security.nginx.nginx_compatibility')"
export experimental="$(yunohost settings get 'security.experimental.enabled')" export experimental="$(yunohost settings get 'security.experimental.security_experimental_enabled')"
ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc" ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc"
cert_status=$(yunohost domain cert status --json) cert_status=$(yunohost domain cert status --json)
@ -92,9 +92,9 @@ do_pre_regen() {
done done
export webadmin_allowlist_enabled=$(yunohost settings get security.webadmin.allowlist.enabled) export webadmin_allowlist_enabled=$(yunohost settings get security.webadmin.webadmin_allowlist_enabled)
if [ "$webadmin_allowlist_enabled" == "True" ]; then if [ "$webadmin_allowlist_enabled" == "True" ]; then
export webadmin_allowlist=$(yunohost settings get security.webadmin.allowlist) export webadmin_allowlist=$(yunohost settings get security.webadmin.webadmin_allowlist)
fi fi
ynh_render_template "yunohost_admin.conf.inc" "${nginx_conf_dir}/yunohost_admin.conf.inc" ynh_render_template "yunohost_admin.conf.inc" "${nginx_conf_dir}/yunohost_admin.conf.inc"
ynh_render_template "yunohost_api.conf.inc" "${nginx_conf_dir}/yunohost_api.conf.inc" ynh_render_template "yunohost_api.conf.inc" "${nginx_conf_dir}/yunohost_api.conf.inc"

View file

@ -22,17 +22,19 @@ do_pre_regen() {
main_domain=$(cat /etc/yunohost/current_host) main_domain=$(cat /etc/yunohost/current_host)
# Support different strategy for security configurations # Support different strategy for security configurations
export compatibility="$(yunohost settings get 'security.postfix.compatibility')" export compatibility="$(yunohost settings get 'security.postfix.postfix_compatibility')"
# Add possibility to specify a relay # Add possibility to specify a relay
# Could be useful with some isp with no 25 port open or more complex setup # Could be useful with some isp with no 25 port open or more complex setup
export relay_port="" export relay_port=""
export relay_user="" export relay_user=""
export relay_host="$(yunohost settings get 'smtp.relay.host')" export relay_host=""
if [ -n "${relay_host}" ]; then export relay_enabled="$(yunohost settings get 'email.smtp.smtp_relay_enabled')"
relay_port="$(yunohost settings get 'smtp.relay.port')" if [ "${relay_enabled}" == "True" ]; then
relay_user="$(yunohost settings get 'smtp.relay.user')" relay_host="$(yunohost settings get 'email.smtp.smtp_relay_host')"
relay_password="$(yunohost settings get 'smtp.relay.password')" relay_port="$(yunohost settings get 'email.smtp.smtp_relay_port')"
relay_user="$(yunohost settings get 'email.smtp.smtp_relay_user')"
relay_password="$(yunohost settings get 'email.smtp.smtp_relay_password')"
# Avoid to display "Relay account paswword" to other users # Avoid to display "Relay account paswword" to other users
touch ${postfix_dir}/sasl_passwd touch ${postfix_dir}/sasl_passwd
@ -54,7 +56,7 @@ do_pre_regen() {
>"${default_dir}/postsrsd" >"${default_dir}/postsrsd"
# adapt it for IPv4-only hosts # adapt it for IPv4-only hosts
ipv6="$(yunohost settings get 'smtp.allow_ipv6')" ipv6="$(yunohost settings get 'email.smtp.smtp_allow_ipv6')"
if [ "$ipv6" == "False" ] || [ ! -f /proc/net/if_inet6 ]; then if [ "$ipv6" == "False" ] || [ ! -f /proc/net/if_inet6 ]; then
sed -i \ sed -i \
's/ \[::ffff:127.0.0.0\]\/104 \[::1\]\/128//g' \ 's/ \[::ffff:127.0.0.0\]\/104 \[::1\]\/128//g' \

View file

@ -16,7 +16,7 @@ do_pre_regen() {
cp dovecot-ldap.conf "${dovecot_dir}/dovecot-ldap.conf" cp dovecot-ldap.conf "${dovecot_dir}/dovecot-ldap.conf"
cp dovecot.sieve "${dovecot_dir}/global_script/dovecot.sieve" cp dovecot.sieve "${dovecot_dir}/global_script/dovecot.sieve"
export pop3_enabled="$(yunohost settings get 'pop3.enabled')" export pop3_enabled="$(yunohost settings get 'email.pop3.pop3_enabled')"
export main_domain=$(cat /etc/yunohost/current_host) export main_domain=$(cat /etc/yunohost/current_host)
export domain_list="$YNH_DOMAINS" export domain_list="$YNH_DOMAINS"

View file

@ -16,7 +16,7 @@ do_pre_regen() {
cp yunohost.conf "${fail2ban_dir}/filter.d/yunohost.conf" cp yunohost.conf "${fail2ban_dir}/filter.d/yunohost.conf"
cp jail.conf "${fail2ban_dir}/jail.conf" cp jail.conf "${fail2ban_dir}/jail.conf"
export ssh_port="$(yunohost settings get 'security.ssh.port')" export ssh_port="$(yunohost settings get 'security.ssh.ssh_port')"
ynh_render_template "yunohost-jails.conf" "${fail2ban_dir}/jail.d/yunohost-jails.conf" ynh_render_template "yunohost-jails.conf" "${fail2ban_dir}/jail.d/yunohost-jails.conf"
} }

View file

@ -3,6 +3,6 @@ backup_dir="$1/conf/ynh"
cp -a "${backup_dir}/current_host" /etc/yunohost/current_host cp -a "${backup_dir}/current_host" /etc/yunohost/current_host
cp -a "${backup_dir}/firewall.yml" /etc/yunohost/firewall.yml cp -a "${backup_dir}/firewall.yml" /etc/yunohost/firewall.yml
[ ! -d "${backup_dir}/domains" ] || cp -a "${backup_dir}/domains" /etc/yunohost/domains [ ! -d "${backup_dir}/domains" ] || cp -a "${backup_dir}/domains" /etc/yunohost/domains
[ ! -e "${backup_dir}/settings.json" ] || cp -a "${backup_dir}/settings.json" "/etc/yunohost/settings.json" [ ! -e "${backup_dir}/settings.yml" ] || cp -a "${backup_dir}/settings.yml" "/etc/yunohost/settings.yml"
[ ! -d "${backup_dir}/dyndns" ] || cp -raT "${backup_dir}/dyndns" "/etc/yunohost/dyndns" [ ! -d "${backup_dir}/dyndns" ] || cp -raT "${backup_dir}/dyndns" "/etc/yunohost/dyndns"
[ ! -d "${backup_dir}/dkim" ] || cp -raT "${backup_dir}/dkim" "/etc/dkim" [ ! -d "${backup_dir}/dkim" ] || cp -raT "${backup_dir}/dkim" "/etc/dkim"

View file

@ -120,8 +120,6 @@
"app_upgrade_several_apps": "سوف يتم تحديث التطبيقات التالية: {apps}", "app_upgrade_several_apps": "سوف يتم تحديث التطبيقات التالية: {apps}",
"ask_new_domain": "نطاق جديد", "ask_new_domain": "نطاق جديد",
"ask_new_path": "مسار جديد", "ask_new_path": "مسار جديد",
"global_settings_setting_security_password_admin_strength": "قوة الكلمة السرية الإدارية",
"global_settings_setting_security_password_user_strength": "قوة الكلمة السرية للمستخدم",
"password_too_simple_1": "يجب أن يكون طول الكلمة السرية على الأقل 8 حروف", "password_too_simple_1": "يجب أن يكون طول الكلمة السرية على الأقل 8 حروف",
"already_up_to_date": "كل شيء على ما يرام. ليس هناك ما يتطلّب تحديثًا.", "already_up_to_date": "كل شيء على ما يرام. ليس هناك ما يتطلّب تحديثًا.",
"service_description_slapd": "يخزّن المستخدمين والنطاقات والمعلومات المتعلقة بها", "service_description_slapd": "يخزّن المستخدمين والنطاقات والمعلومات المتعلقة بها",
@ -159,6 +157,8 @@
"diagnosis_description_dnsrecords": "تسجيلات خدمة DNS", "diagnosis_description_dnsrecords": "تسجيلات خدمة DNS",
"diagnosis_description_ip": "الإتصال بالإنترنت", "diagnosis_description_ip": "الإتصال بالإنترنت",
"diagnosis_description_basesystem": "النظام الأساسي", "diagnosis_description_basesystem": "النظام الأساسي",
"global_settings_setting_admin_strength": "قوة الكلمة السرية الإدارية",
"global_settings_setting_user_strength": "قوة الكلمة السرية للمستخدم",
"field_invalid": "الحقل غير صحيح : '{}'", "field_invalid": "الحقل غير صحيح : '{}'",
"diagnosis_ignored_issues": "(+ {nb_ignored} مشاكل تم تجاهلها)" "diagnosis_ignored_issues": "(+ {nb_ignored} مشاكل تم تجاهلها)"
} }

View file

@ -155,12 +155,7 @@
"global_settings_cant_write_settings": "No s'ha pogut escriure el fitxer de configuració, raó: {reason}", "global_settings_cant_write_settings": "No s'ha pogut escriure el fitxer de configuració, raó: {reason}",
"global_settings_key_doesnt_exists": "La clau « {settings_key} » no existeix en la configuració global, podeu veure totes les claus disponibles executant «yunohost settings list »", "global_settings_key_doesnt_exists": "La clau « {settings_key} » no existeix en la configuració global, podeu veure totes les claus disponibles executant «yunohost settings list »",
"global_settings_reset_success": "S'ha fet una còpia de seguretat de la configuració anterior a {path}", "global_settings_reset_success": "S'ha fet una còpia de seguretat de la configuració anterior a {path}",
"global_settings_setting_security_nginx_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor web NGINX. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)",
"global_settings_setting_security_password_admin_strength": "Robustesa de la contrasenya d'administrador",
"global_settings_setting_security_password_user_strength": "Robustesa de la contrasenya de l'usuari",
"global_settings_setting_security_ssh_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor SSH. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)",
"global_settings_unknown_setting_from_settings_file": "Clau de configuració desconeguda: «{setting_key}», refusada i guardada a /etc/yunohost/settings-unknown.json", "global_settings_unknown_setting_from_settings_file": "Clau de configuració desconeguda: «{setting_key}», refusada i guardada a /etc/yunohost/settings-unknown.json",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permetre la clau d'hoste DSA (obsolet) per la configuració del servei SSH",
"global_settings_unknown_type": "Situació inesperada, la configuració {setting} sembla tenir el tipus {unknown_type} però no és un tipus reconegut pel sistema.", "global_settings_unknown_type": "Situació inesperada, la configuració {setting} sembla tenir el tipus {unknown_type} però no és un tipus reconegut pel sistema.",
"good_practices_about_admin_password": "Esteu a punt de definir una nova contrasenya d'administrador. La contrasenya ha de tenir un mínim de 8 caràcters; tot i que és de bona pràctica utilitzar una contrasenya més llarga (és a dir una frase de contrasenya) i/o utilitzar diferents tipus de caràcters (majúscules, minúscules, dígits i caràcters especials).", "good_practices_about_admin_password": "Esteu a punt de definir una nova contrasenya d'administrador. La contrasenya ha de tenir un mínim de 8 caràcters; tot i que és de bona pràctica utilitzar una contrasenya més llarga (és a dir una frase de contrasenya) i/o utilitzar diferents tipus de caràcters (majúscules, minúscules, dígits i caràcters especials).",
"hook_exec_failed": "No s'ha pogut executar el script: {path}", "hook_exec_failed": "No s'ha pogut executar el script: {path}",
@ -207,7 +202,6 @@
"log_tools_reboot": "Reinicia el servidor", "log_tools_reboot": "Reinicia el servidor",
"already_up_to_date": "No hi ha res a fer. Tot està actualitzat.", "already_up_to_date": "No hi ha res a fer. Tot està actualitzat.",
"dpkg_lock_not_available": "No es pot utilitzar aquesta comanda en aquest moment ja que sembla que un altre programa està utilitzant el lock de dpkg (el gestor de paquets del sistema)", "dpkg_lock_not_available": "No es pot utilitzar aquesta comanda en aquest moment ja que sembla que un altre programa està utilitzant el lock de dpkg (el gestor de paquets del sistema)",
"global_settings_setting_security_postfix_compatibility": "Solució de compromís entre compatibilitat i seguretat pel servidor Postfix. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)",
"mail_alias_remove_failed": "No s'han pogut eliminar els àlies del correu «{mail}»", "mail_alias_remove_failed": "No s'han pogut eliminar els àlies del correu «{mail}»",
"mail_domain_unknown": "El domini «{domain}» de l'adreça de correu no és vàlid. Utilitzeu un domini administrat per aquest servidor.", "mail_domain_unknown": "El domini «{domain}» de l'adreça de correu no és vàlid. Utilitzeu un domini administrat per aquest servidor.",
"mail_forward_remove_failed": "No s'han pogut eliminar el reenviament de correu «{mail}»", "mail_forward_remove_failed": "No s'han pogut eliminar el reenviament de correu «{mail}»",
@ -471,7 +465,6 @@
"diagnosis_services_running": "El servei {service} s'està executant!", "diagnosis_services_running": "El servei {service} s'està executant!",
"diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!", "diagnosis_services_conf_broken": "La configuració pel servei {service} està trencada!",
"diagnosis_ports_needed_by": "És necessari exposar aquest port per a les funcions {category} (servei {service})", "diagnosis_ports_needed_by": "És necessari exposar aquest port per a les funcions {category} (servei {service})",
"global_settings_setting_pop3_enabled": "Activa el protocol POP3 per al servidor de correu",
"log_app_action_run": "Executa l'acció de l'aplicació «{}»", "log_app_action_run": "Executa l'acció de l'aplicació «{}»",
"diagnosis_never_ran_yet": "Sembla que el servidor s'ha configurat recentment i encara no hi cap informe de diagnòstic per mostrar. S'ha d'executar un diagnòstic complet primer, ja sigui des de la pàgina web d'administració o utilitzant la comanda «yunohost diagnosis run» al terminal.", "diagnosis_never_ran_yet": "Sembla que el servidor s'ha configurat recentment i encara no hi cap informe de diagnòstic per mostrar. S'ha d'executar un diagnòstic complet primer, ja sigui des de la pàgina web d'administració o utilitzant la comanda «yunohost diagnosis run» al terminal.",
"diagnosis_description_web": "Web", "diagnosis_description_web": "Web",
@ -506,7 +499,6 @@
"diagnosis_http_hairpinning_issue": "Sembla que la vostra xarxa no té el hairpinning activat.", "diagnosis_http_hairpinning_issue": "Sembla que la vostra xarxa no té el hairpinning activat.",
"diagnosis_http_nginx_conf_not_up_to_date": "La configuració NGINX d'aquest domini sembla que ha estat modificada manualment, i no deixa que YunoHost diagnostiqui si és accessible amb HTTP.", "diagnosis_http_nginx_conf_not_up_to_date": "La configuració NGINX d'aquest domini sembla que ha estat modificada manualment, i no deixa que YunoHost diagnostiqui si és accessible amb HTTP.",
"diagnosis_http_nginx_conf_not_up_to_date_details": "Per arreglar el problema, mireu les diferències amb la línia d'ordres utilitzant <cmd>yunohost tools regen-conf nginx --dry-run --with-diff</cmd> i si els canvis us semblen bé els podeu fer efectius utilitzant <cmd>yunohost tools regen-conf nginx --force</cmd>.", "diagnosis_http_nginx_conf_not_up_to_date_details": "Per arreglar el problema, mireu les diferències amb la línia d'ordres utilitzant <cmd>yunohost tools regen-conf nginx --dry-run --with-diff</cmd> i si els canvis us semblen bé els podeu fer efectius utilitzant <cmd>yunohost tools regen-conf nginx --force</cmd>.",
"global_settings_setting_smtp_allow_ipv6": "Permet l'ús de IPv6 per rebre i enviar correus electrònics",
"diagnosis_mail_ehlo_unreachable_details": "No s'ha pogut establir una connexió amb el vostre servidor en el port 25 amb IPv{ipversion}. Sembla que el servidor no és accessible.<br>1. La causa més comú per aquest problema és que el port 25 <a href='https://yunohost.org/isp_box_config'>no està correctament redireccionat cap al vostre servidor</a>.<br>2. També us hauríeu d'assegurar que el servei postfix estigui funcionant.<br>3. En configuracions més complexes: assegureu-vos que que no hi hagi cap tallafoc ni reverse-proxy interferint.", "diagnosis_mail_ehlo_unreachable_details": "No s'ha pogut establir una connexió amb el vostre servidor en el port 25 amb IPv{ipversion}. Sembla que el servidor no és accessible.<br>1. La causa més comú per aquest problema és que el port 25 <a href='https://yunohost.org/isp_box_config'>no està correctament redireccionat cap al vostre servidor</a>.<br>2. També us hauríeu d'assegurar que el servei postfix estigui funcionant.<br>3. En configuracions més complexes: assegureu-vos que que no hi hagi cap tallafoc ni reverse-proxy interferint.",
"diagnosis_mail_ehlo_wrong_details": "El EHLO rebut pel servidor de diagnòstic remot amb IPv{ipversion} és diferent al domini del vostre servidor.<br>EHLO rebut: <code>{wrong_ehlo}</code><br>Esperat: <code>{right_ehlo}</code><br>La causa més habitual d'aquest problema és que el port 25 <a href='https://yunohost.org/isp_box_config'>no està correctament reenviat cap al vostre servidor</a>. També podeu comprovar que no hi hagi un tallafocs o un reverse-proxy interferint.", "diagnosis_mail_ehlo_wrong_details": "El EHLO rebut pel servidor de diagnòstic remot amb IPv{ipversion} és diferent al domini del vostre servidor.<br>EHLO rebut: <code>{wrong_ehlo}</code><br>Esperat: <code>{right_ehlo}</code><br>La causa més habitual d'aquest problema és que el port 25 <a href='https://yunohost.org/isp_box_config'>no està correctament reenviat cap al vostre servidor</a>. També podeu comprovar que no hi hagi un tallafocs o un reverse-proxy interferint.",
"diagnosis_mail_fcrdns_dns_missing": "No hi ha cap DNS invers definit per IPv{ipversion}. Alguns correus electrònics poden no entregar-se o poden ser marcats com a correu brossa.", "diagnosis_mail_fcrdns_dns_missing": "No hi ha cap DNS invers definit per IPv{ipversion}. Alguns correus electrònics poden no entregar-se o poden ser marcats com a correu brossa.",
@ -533,8 +525,6 @@
"app_packaging_format_not_supported": "No es pot instal·lar aquesta aplicació ja que el format del paquet no és compatible amb la versió de YunoHost del sistema. Hauríeu de considerar actualitzar el sistema.", "app_packaging_format_not_supported": "No es pot instal·lar aquesta aplicació ja que el format del paquet no és compatible amb la versió de YunoHost del sistema. Hauríeu de considerar actualitzar el sistema.",
"diagnosis_dns_try_dyndns_update_force": "La configuració DNS d'aquest domini hauria de ser gestionada automàticament per YunoHost. Si aquest no és el cas, podeu intentar forçar-ne l'actualització utilitzant <cmd>yunohost dyndns update --force</cmd>.", "diagnosis_dns_try_dyndns_update_force": "La configuració DNS d'aquest domini hauria de ser gestionada automàticament per YunoHost. Si aquest no és el cas, podeu intentar forçar-ne l'actualització utilitzant <cmd>yunohost dyndns update --force</cmd>.",
"regenconf_need_to_explicitly_specify_ssh": "La configuració ssh ha estat modificada manualment, però heu d'especificar explícitament la categoria «ssh» amb --force per fer realment els canvis.", "regenconf_need_to_explicitly_specify_ssh": "La configuració ssh ha estat modificada manualment, però heu d'especificar explícitament la categoria «ssh» amb --force per fer realment els canvis.",
"global_settings_setting_backup_compress_tar_archives": "Comprimir els arxius (.tar.gz) en lloc d'arxius no comprimits (.tar) al crear noves còpies de seguretat. N.B.: activar aquesta opció permet fer arxius de còpia de seguretat més lleugers, però el procés inicial de còpia de seguretat serà significativament més llarg i més exigent a nivell de CPU.",
"global_settings_setting_smtp_relay_host": "L'amfitrió de tramesa SMTP que s'ha d'utilitzar per enviar correus electrònics en lloc d'aquesta instància de YunoHost. És útil si esteu en una de les següents situacions: el port 25 està bloquejat per el vostre proveïdor d'accés a internet o proveïdor de servidor privat virtual, si teniu una IP residencial llistada a DUHL, si no podeu configurar el DNS invers o si el servidor no està directament exposat a internet i voleu utilitzar-ne un altre per enviar correus electrònics.",
"unknown_main_domain_path": "Domini o ruta desconeguda per a «{app}». Heu d'especificar un domini i una ruta per a poder especificar una URL per al permís.", "unknown_main_domain_path": "Domini o ruta desconeguda per a «{app}». Heu d'especificar un domini i una ruta per a poder especificar una URL per al permís.",
"show_tile_cant_be_enabled_for_regex": "No podeu activar «show_title» ara, perquè la URL per al permís «{permission}» és una expressió regular", "show_tile_cant_be_enabled_for_regex": "No podeu activar «show_title» ara, perquè la URL per al permís «{permission}» és una expressió regular",
"show_tile_cant_be_enabled_for_url_not_defined": "No podeu activar «show_title» ara, perquè primer s'ha de definir una URL per al permís «{permission}»", "show_tile_cant_be_enabled_for_url_not_defined": "No podeu activar «show_title» ara, perquè primer s'ha de definir una URL per al permís «{permission}»",
@ -568,5 +558,14 @@
"diagnosis_sshd_config_inconsistent": "Sembla que el port SSH s'ha modificat manualment a /etc/ssh/sshd_config. Des de YunoHost 4.2, hi ha un nou paràmetre global «security.ssh.port» per evitar modificar manualment la configuració.", "diagnosis_sshd_config_inconsistent": "Sembla que el port SSH s'ha modificat manualment a /etc/ssh/sshd_config. Des de YunoHost 4.2, hi ha un nou paràmetre global «security.ssh.port» per evitar modificar manualment la configuració.",
"diagnosis_sshd_config_insecure": "Sembla que la configuració SSH s'ha modificat manualment, i no es segura ha que no conté la directiva «AllowGroups» o «AllowUsers» per limitar l'accés a usuaris autoritzats.", "diagnosis_sshd_config_insecure": "Sembla que la configuració SSH s'ha modificat manualment, i no es segura ha que no conté la directiva «AllowGroups» o «AllowUsers» per limitar l'accés a usuaris autoritzats.",
"backup_create_size_estimation": "L'arxiu tindrà aproximadament {size} de dades.", "backup_create_size_estimation": "L'arxiu tindrà aproximadament {size} de dades.",
"app_restore_script_failed": "S'ha produït un error en el script de restauració de l'aplicació" "app_restore_script_failed": "S'ha produït un error en el script de restauració de l'aplicació",
"global_settings_setting_backup_compress_tar_archives_help": "Comprimir els arxius (.tar.gz) en lloc d'arxius no comprimits (.tar) al crear noves còpies de seguretat. N.B.: activar aquesta opció permet fer arxius de còpia de seguretat més lleugers, però el procés inicial de còpia de seguretat serà significativament més llarg i més exigent a nivell de CPU.",
"global_settings_setting_nginx_compatibility_help": "Solució de compromís entre compatibilitat i seguretat pel servidor web NGINX. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)",
"global_settings_setting_admin_strength": "Robustesa de la contrasenya d'administrador",
"global_settings_setting_user_strength": "Robustesa de la contrasenya de l'usuari",
"global_settings_setting_postfix_compatibility_help": "Solució de compromís entre compatibilitat i seguretat pel servidor Postfix. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)",
"global_settings_setting_ssh_compatibility_help": "Solució de compromís entre compatibilitat i seguretat pel servidor SSH. Afecta els criptògrafs (i altres aspectes relacionats amb la seguretat)",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "Permetre la clau d'hoste DSA (obsolet) per la configuració del servei SSH",
"global_settings_setting_smtp_allow_ipv6_help": "Permet l'ús de IPv6 per rebre i enviar correus electrònics",
"global_settings_setting_smtp_relay_enabled_help": "L'amfitrió de tramesa SMTP que s'ha d'utilitzar per enviar correus electrònics en lloc d'aquesta instància de YunoHost. És útil si esteu en una de les següents situacions: el port 25 està bloquejat per el vostre proveïdor d'accés a internet o proveïdor de servidor privat virtual, si teniu una IP residencial llistada a DUHL, si no podeu configurar el DNS invers o si el servidor no està directament exposat a internet i voleu utilitzar-ne un altre per enviar correus electrònics."
} }

View file

@ -50,18 +50,18 @@
"good_practices_about_user_password": "Nyní zvolte nové heslo uživatele. Heslo by mělo být minimálně 8 znaků dlouhé, avšak je dobrou taktikou jej mít delší (např. použít více slov) a použít kombinaci znaků (velké, malé, čísla a speciální znaky).", "good_practices_about_user_password": "Nyní zvolte nové heslo uživatele. Heslo by mělo být minimálně 8 znaků dlouhé, avšak je dobrou taktikou jej mít delší (např. použít více slov) a použít kombinaci znaků (velké, malé, čísla a speciální znaky).",
"good_practices_about_admin_password": "Nyní zvolte nové administrační heslo. Heslo by mělo být minimálně 8 znaků dlouhé, avšak je dobrou taktikou jej mít delší (např. použít více slov) a použít kombinaci znaků (velké, malé, čísla a speciílní znaky).", "good_practices_about_admin_password": "Nyní zvolte nové administrační heslo. Heslo by mělo být minimálně 8 znaků dlouhé, avšak je dobrou taktikou jej mít delší (např. použít více slov) a použít kombinaci znaků (velké, malé, čísla a speciílní znaky).",
"global_settings_unknown_type": "Neočekávaná situace, nastavení {setting} deklaruje typ {unknown_type} ale toto není systémem podporováno.", "global_settings_unknown_type": "Neočekávaná situace, nastavení {setting} deklaruje typ {unknown_type} ale toto není systémem podporováno.",
"global_settings_setting_backup_compress_tar_archives": "Komprimovat nové zálohy (.tar.gz) namísto nekomprimovaných (.tar). Poznámka: povolení této volby znamená objemově menší soubory záloh, avšak zálohování bude trvat déle a bude více zatěžovat CPU.",
"global_settings_setting_smtp_relay_password": "SMTP relay heslo uživatele/hostitele", "global_settings_setting_smtp_relay_password": "SMTP relay heslo uživatele/hostitele",
"global_settings_setting_smtp_relay_user": "SMTP relay uživatelské jméno/účet", "global_settings_setting_smtp_relay_user": "SMTP relay uživatelské jméno/účet",
"global_settings_setting_smtp_relay_port": "SMTP relay port", "global_settings_setting_smtp_relay_port": "SMTP relay port",
"global_settings_setting_smtp_relay_host": "Použít SMTP relay hostitele pro odesílání emailů místo této YunoHost instance. Užitečné v různých situacích: port 25 je blokován vaším ISP nebo VPS poskytovatelem, IP adresa je na blacklistu (např. DUHL), nemůžete nastavit reverzní DNS záznam nebo tento server není přímo připojen do internetu a vy chcete použít jiný server k odesílání emailů.",
"global_settings_setting_smtp_allow_ipv6": "Povolit použití IPv6 pro příjem a odesílání emailů",
"global_settings_setting_ssowat_panel_overlay_enabled": "Povolit SSOwat překryvný panel", "global_settings_setting_ssowat_panel_overlay_enabled": "Povolit SSOwat překryvný panel",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Povolit použití (zastaralého) DSA klíče hostitele pro konfiguraci SSH služby",
"global_settings_unknown_setting_from_settings_file": "Neznámý klíč v nastavení: '{setting_key}', zrušte jej a uložte v /etc/yunohost/settings-unknown.json", "global_settings_unknown_setting_from_settings_file": "Neznámý klíč v nastavení: '{setting_key}', zrušte jej a uložte v /etc/yunohost/settings-unknown.json",
"global_settings_setting_security_ssh_port": "SSH port", "global_settings_setting_backup_compress_tar_archives_help": "Komprimovat nové zálohy (.tar.gz) namísto nekomprimovaných (.tar). Poznámka: povolení této volby znamená objemově menší soubory záloh, avšak zálohování bude trvat déle a bude více zatěžovat CPU.",
"global_settings_setting_security_postfix_compatibility": "Kompromis mezi kompatibilitou a bezpečností Postfix serveru. Ovlivní šifry a další související bezpečnostní nastavení", "global_settings_setting_admin_strength": "Síla administračního hesla",
"global_settings_setting_security_ssh_compatibility": "Kompromis mezi kompatibilitou a bezpečností SSH serveru. Ovlivní šifry a další související bezpečnostní nastavení", "global_settings_setting_user_strength": "Síla uživatelského hesla",
"global_settings_setting_security_password_user_strength": "Síla uživatelského hesla", "global_settings_setting_postfix_compatibility_help": "Kompromis mezi kompatibilitou a bezpečností Postfix serveru. Ovlivní šifry a další související bezpečnostní nastavení",
"global_settings_setting_security_password_admin_strength": "Síla administračního hesla" "global_settings_setting_ssh_compatibility_help": "Kompromis mezi kompatibilitou a bezpečností SSH serveru. Ovlivní šifry a další související bezpečnostní nastavení",
"global_settings_setting_ssh_port": "SSH port",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "Povolit použití (zastaralého) DSA klíče hostitele pro konfiguraci SSH služby",
"global_settings_setting_smtp_allow_ipv6_help": "Povolit použití IPv6 pro příjem a odesílání emailů",
"global_settings_setting_smtp_relay_enabled_help": "Použít SMTP relay hostitele pro odesílání emailů místo této YunoHost instance. Užitečné v různých situacích: port 25 je blokován vaším ISP nebo VPS poskytovatelem, IP adresa je na blacklistu (např. DUHL), nemůžete nastavit reverzní DNS záznam nebo tento server není přímo připojen do internetu a vy chcete použít jiný server k odesílání emailů."
} }

View file

@ -221,7 +221,6 @@
"app_action_broke_system": "Diese Aktion scheint diese wichtigen Dienste unterbrochen zu haben: {services}", "app_action_broke_system": "Diese Aktion scheint diese wichtigen Dienste unterbrochen zu haben: {services}",
"apps_already_up_to_date": "Alle Apps sind bereits aktuell", "apps_already_up_to_date": "Alle Apps sind bereits aktuell",
"backup_copying_to_organize_the_archive": "Kopieren von {size} MB, um das Archiv zu organisieren", "backup_copying_to_organize_the_archive": "Kopieren von {size} MB, um das Archiv zu organisieren",
"global_settings_setting_security_ssh_compatibility": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den SSH-Server. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)",
"group_deleted": "Gruppe '{group}' gelöscht", "group_deleted": "Gruppe '{group}' gelöscht",
"group_deletion_failed": "Konnte Gruppe '{group}' nicht löschen: {error}", "group_deletion_failed": "Konnte Gruppe '{group}' nicht löschen: {error}",
"dyndns_provider_unreachable": "DynDNS-Anbieter {provider} kann nicht erreicht werden: Entweder ist dein YunoHost nicht korrekt mit dem Internet verbunden oder der Dynette-Server ist ausgefallen.", "dyndns_provider_unreachable": "DynDNS-Anbieter {provider} kann nicht erreicht werden: Entweder ist dein YunoHost nicht korrekt mit dem Internet verbunden oder der Dynette-Server ist ausgefallen.",
@ -232,14 +231,11 @@
"group_update_failed": "Kann Gruppe '{group}' nicht aktualisieren: {error}", "group_update_failed": "Kann Gruppe '{group}' nicht aktualisieren: {error}",
"log_does_exists": "Es gibt kein Operationsprotokoll mit dem Namen'{log}', verwende 'yunohost log list', um alle verfügbaren Operationsprotokolle anzuzeigen", "log_does_exists": "Es gibt kein Operationsprotokoll mit dem Namen'{log}', verwende 'yunohost log list', um alle verfügbaren Operationsprotokolle anzuzeigen",
"log_operation_unit_unclosed_properly": "Die Operationseinheit wurde nicht richtig geschlossen", "log_operation_unit_unclosed_properly": "Die Operationseinheit wurde nicht richtig geschlossen",
"global_settings_setting_security_postfix_compatibility": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den Postfix-Server. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)",
"global_settings_unknown_type": "Unerwartete Situation, die Einstellung {setting} scheint den Typ {unknown_type} zu haben, ist aber kein vom System unterstützter Typ.", "global_settings_unknown_type": "Unerwartete Situation, die Einstellung {setting} scheint den Typ {unknown_type} zu haben, ist aber kein vom System unterstützter Typ.",
"dpkg_is_broken": "Du kannst das gerade nicht tun, weil dpkg/APT (der Systempaketmanager) in einem defekten Zustand zu sein scheint... Du kannst versuchen, dieses Problem zu lösen, indem du dich über SSH verbindest und `sudo apt install --fix-broken` sowie/oder `sudo dpkg --configure -a` ausführst.", "dpkg_is_broken": "Du kannst das gerade nicht tun, weil dpkg/APT (der Systempaketmanager) in einem defekten Zustand zu sein scheint... Du kannst versuchen, dieses Problem zu lösen, indem du dich über SSH verbindest und `sudo apt install --fix-broken` sowie/oder `sudo dpkg --configure -a` ausführst.",
"global_settings_unknown_setting_from_settings_file": "Unbekannter Schlüssel in den Einstellungen: '{setting_key}', verwerfen und speichern in /etc/yunohost/settings-unknown.json", "global_settings_unknown_setting_from_settings_file": "Unbekannter Schlüssel in den Einstellungen: '{setting_key}', verwerfen und speichern in /etc/yunohost/settings-unknown.json",
"log_link_to_log": "Vollständiges Log dieser Operation: '<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>'", "log_link_to_log": "Vollständiges Log dieser Operation: '<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>'",
"log_help_to_get_log": "Um das Protokoll der Operation '{desc}' anzuzeigen, verwende den Befehl 'yunohost log show {name}'", "log_help_to_get_log": "Um das Protokoll der Operation '{desc}' anzuzeigen, verwende den Befehl 'yunohost log show {name}'",
"global_settings_setting_security_nginx_compatibility": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den Webserver NGINX. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Erlaubt die Verwendung eines (veralteten) DSA-Hostkeys für die SSH-Daemon-Konfiguration",
"log_app_remove": "Entferne die Applikation '{}'", "log_app_remove": "Entferne die Applikation '{}'",
"global_settings_cant_open_settings": "Einstellungsdatei konnte nicht geöffnet werden, Grund: {reason}", "global_settings_cant_open_settings": "Einstellungsdatei konnte nicht geöffnet werden, Grund: {reason}",
"global_settings_cant_write_settings": "Einstellungsdatei konnte nicht gespeichert werden, Grund: {reason}", "global_settings_cant_write_settings": "Einstellungsdatei konnte nicht gespeichert werden, Grund: {reason}",
@ -252,12 +248,10 @@
"log_help_to_get_failed_log": "Der Vorgang'{desc}' konnte nicht abgeschlossen werden. Bitte teile das vollständige Protokoll dieser Operation mit dem Befehl 'yunohost log share {name}', um Hilfe zu erhalten", "log_help_to_get_failed_log": "Der Vorgang'{desc}' konnte nicht abgeschlossen werden. Bitte teile das vollständige Protokoll dieser Operation mit dem Befehl 'yunohost log share {name}', um Hilfe zu erhalten",
"backup_no_uncompress_archive_dir": "Dieses unkomprimierte Archivverzeichnis gibt es nicht", "backup_no_uncompress_archive_dir": "Dieses unkomprimierte Archivverzeichnis gibt es nicht",
"log_app_change_url": "Ändere die URL der Applikation '{}'", "log_app_change_url": "Ändere die URL der Applikation '{}'",
"global_settings_setting_security_password_user_strength": "Stärke des Anmeldepassworts",
"good_practices_about_user_password": "Du bist nun dabei, ein neues Nutzerpasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein - es ist jedoch empfehlenswert, ein längeres Passwort (z.B. eine Passphrase) und/oder verschiedene Arten von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.", "good_practices_about_user_password": "Du bist nun dabei, ein neues Nutzerpasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein - es ist jedoch empfehlenswert, ein längeres Passwort (z.B. eine Passphrase) und/oder verschiedene Arten von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.",
"log_link_to_failed_log": "Der Vorgang konnte nicht abgeschlossen werden '{desc}'. Bitte gib das vollständige Protokoll dieser Operation mit <a href=\"#/tools/logs/{name}\">Klicken Sie hier</a> an, um Hilfe zu erhalten", "log_link_to_failed_log": "Der Vorgang konnte nicht abgeschlossen werden '{desc}'. Bitte gib das vollständige Protokoll dieser Operation mit <a href=\"#/tools/logs/{name}\">Klicken Sie hier</a> an, um Hilfe zu erhalten",
"backup_cant_mount_uncompress_archive": "Das unkomprimierte Archiv konnte nicht als schreibgeschützt gemountet werden", "backup_cant_mount_uncompress_archive": "Das unkomprimierte Archiv konnte nicht als schreibgeschützt gemountet werden",
"backup_csv_addition_failed": "Es konnten keine Dateien zur Sicherung in die CSV-Datei hinzugefügt werden", "backup_csv_addition_failed": "Es konnten keine Dateien zur Sicherung in die CSV-Datei hinzugefügt werden",
"global_settings_setting_security_password_admin_strength": "Stärke des Admin-Passworts",
"global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst", "global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key}' existiert nicht in den globalen Einstellungen, du kannst alle verfügbaren Schlüssel sehen, indem du 'yunohost settings list' ausführst",
"log_app_makedefault": "Mache '{}' zur Standard-Applikation", "log_app_makedefault": "Mache '{}' zur Standard-Applikation",
"hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path} nicht lesen. Fehler: {msg}. Unformatierter Inhalt: {raw_content}", "hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path} nicht lesen. Fehler: {msg}. Unformatierter Inhalt: {raw_content}",
@ -436,13 +430,9 @@
"global_settings_setting_smtp_relay_password": "SMTP Relay Host Passwort", "global_settings_setting_smtp_relay_password": "SMTP Relay Host Passwort",
"global_settings_setting_smtp_relay_user": "SMTP Relay Benutzer Account", "global_settings_setting_smtp_relay_user": "SMTP Relay Benutzer Account",
"global_settings_setting_smtp_relay_port": "SMTP Relay Port", "global_settings_setting_smtp_relay_port": "SMTP Relay Port",
"global_settings_setting_smtp_allow_ipv6": "Erlaube die Nutzung von IPv6 um Mails zu empfangen und zu versenden",
"global_settings_setting_pop3_enabled": "Aktiviere das POP3 Protokoll für den Mailserver",
"domain_cannot_remove_main_add_new_one": "Sie können '{domain}' nicht entfernen, da es die Hauptdomäne und Ihre einzige Domäne ist. Sie müssen zuerst eine andere Domäne mit 'yunohost domain add <domäne.com>' hinzufügen, dann als Hauptdomäne mit 'yunohost domain main-domain -n <domäne.com>' festlegen und dann können Sie die Domäne '{domain}' mit 'yunohost domain remove {domain}' entfernen'.'", "domain_cannot_remove_main_add_new_one": "Sie können '{domain}' nicht entfernen, da es die Hauptdomäne und Ihre einzige Domäne ist. Sie müssen zuerst eine andere Domäne mit 'yunohost domain add <domäne.com>' hinzufügen, dann als Hauptdomäne mit 'yunohost domain main-domain -n <domäne.com>' festlegen und dann können Sie die Domäne '{domain}' mit 'yunohost domain remove {domain}' entfernen'.'",
"diagnosis_rootfstotalspace_critical": "Das Root-Filesystem hat noch freien Speicher von {space}. Das ist besorngiserregend! Der Speicher wird schnell aufgebraucht sein. 16 GB für das Root-Filesystem werden empfohlen.", "diagnosis_rootfstotalspace_critical": "Das Root-Filesystem hat noch freien Speicher von {space}. Das ist besorngiserregend! Der Speicher wird schnell aufgebraucht sein. 16 GB für das Root-Filesystem werden empfohlen.",
"diagnosis_rootfstotalspace_warning": "Das Root-Filesystem hat noch freien Speicher von {space}. Möglich, dass das in Ordnung ist. Vielleicht ist er aber auch schneller aufgebraucht. 16 GB für das Root-Filesystem werden empfohlen.", "diagnosis_rootfstotalspace_warning": "Das Root-Filesystem hat noch freien Speicher von {space}. Möglich, dass das in Ordnung ist. Vielleicht ist er aber auch schneller aufgebraucht. 16 GB für das Root-Filesystem werden empfohlen.",
"global_settings_setting_smtp_relay_host": "Zu verwendender SMTP-Relay-Host um E-Mails zu versenden. Er wird anstelle dieser YunoHost-Instanz verwendet. Nützlich, wenn du in einer der folgenden Situationen bist: Dein ISP- oder VPS-Provider hat deinen Port 25 geblockt, eine deinen residentiellen IPs ist auf DUHL gelistet, du kannst keinen Reverse-DNS konfigurieren oder dieser Server ist nicht direkt mit dem Internet verbunden und du möchtest einen anderen verwenden, um E-Mails zu versenden.",
"global_settings_setting_backup_compress_tar_archives": "Beim Erstellen von Backups die Archive komprimieren (.tar.gz) anstelle von unkomprimierten Archiven (.tar). N.B. : Diese Option ergibt leichtere Backup-Archive, aber das initiale Backupprozedere wird länger dauern und mehr CPU brauchen.",
"log_remove_on_failed_restore": "Entfernen von '{}' nach einer fehlgeschlagenen Wiederherstellung aus einem Sicherungsarchiv", "log_remove_on_failed_restore": "Entfernen von '{}' nach einer fehlgeschlagenen Wiederherstellung aus einem Sicherungsarchiv",
"log_backup_restore_app": "Wiederherstellen von '{}' aus einem Sicherungsarchiv", "log_backup_restore_app": "Wiederherstellen von '{}' aus einem Sicherungsarchiv",
"log_backup_restore_system": "System aus einem Sicherungsarchiv wiederherstellen", "log_backup_restore_system": "System aus einem Sicherungsarchiv wiederherstellen",
@ -548,7 +538,6 @@
"migration_ldap_migration_failed_trying_to_rollback": "Migrieren war nicht möglich... Versuch, ein Rollback des Systems durchzuführen.", "migration_ldap_migration_failed_trying_to_rollback": "Migrieren war nicht möglich... Versuch, ein Rollback des Systems durchzuführen.",
"migration_ldap_backup_before_migration": "Vor der eigentlichen Migration ein Backup der LDAP-Datenbank und der Applikations-Einstellungen erstellen.", "migration_ldap_backup_before_migration": "Vor der eigentlichen Migration ein Backup der LDAP-Datenbank und der Applikations-Einstellungen erstellen.",
"global_settings_setting_ssowat_panel_overlay_enabled": "Das SSOwat-Overlay-Panel aktivieren", "global_settings_setting_ssowat_panel_overlay_enabled": "Das SSOwat-Overlay-Panel aktivieren",
"global_settings_setting_security_ssh_port": "SSH-Port",
"diagnosis_sshd_config_inconsistent_details": "Bitte führe <cmd>yunohost settings set security.ssh.port -v YOUR_SSH_PORT</cmd> aus, um den SSH-Port festzulegen, und prüfe <cmd> yunohost tools regen-conf ssh --dry-run --with-diff</cmd> und <cmd>yunohost tools regen-conf ssh --force</cmd> um deine Konfiguration auf die YunoHost-Empfehlung zurückzusetzen.", "diagnosis_sshd_config_inconsistent_details": "Bitte führe <cmd>yunohost settings set security.ssh.port -v YOUR_SSH_PORT</cmd> aus, um den SSH-Port festzulegen, und prüfe <cmd> yunohost tools regen-conf ssh --dry-run --with-diff</cmd> und <cmd>yunohost tools regen-conf ssh --force</cmd> um deine Konfiguration auf die YunoHost-Empfehlung zurückzusetzen.",
"regex_incompatible_with_tile": "/!\\ Packagers! Für Berechtigung '{permission}' ist show_tile auf 'true' gesetzt und deshalb kannst du keine regex-URL als Hauptdomäne setzen", "regex_incompatible_with_tile": "/!\\ Packagers! Für Berechtigung '{permission}' ist show_tile auf 'true' gesetzt und deshalb kannst du keine regex-URL als Hauptdomäne setzen",
"permission_cant_add_to_all_users": "Die Berechtigung {permission} kann nicht für allen Konten hinzugefügt werden.", "permission_cant_add_to_all_users": "Die Berechtigung {permission} kann nicht für allen Konten hinzugefügt werden.",
@ -580,8 +569,6 @@
"yunohost_postinstall_end_tip": "Post-install ist fertig! Um das Setup abzuschliessen, wird empfohlen:\n - ein erstes Konto über den Bereich 'Konto' 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.", "yunohost_postinstall_end_tip": "Post-install ist fertig! Um das Setup abzuschliessen, wird empfohlen:\n - ein erstes Konto über den Bereich 'Konto' 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": "Das Konto '{user}' ist bereits vorhanden", "user_already_exists": "Das Konto '{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 aktualisieren", "disk_space_not_sufficient_update": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu aktualisieren",
"disk_space_not_sufficient_install": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu installieren", "disk_space_not_sufficient_install": "Es ist nicht genügend Speicherplatz frei, um diese Applikation zu installieren",
"danger": "Warnung:", "danger": "Warnung:",
@ -618,8 +605,6 @@
"domain_unknown": "Domäne '{domain}' unbekannt", "domain_unknown": "Domäne '{domain}' unbekannt",
"ldap_server_is_down_restart_it": "Der LDAP-Dienst ist nicht erreichbar, versuche ihn neu zu starten...", "ldap_server_is_down_restart_it": "Der LDAP-Dienst ist nicht erreichbar, versuche ihn neu zu starten...",
"user_import_bad_file": "Deine CSV-Datei ist nicht korrekt formatiert und wird daher ignoriert, um einen möglichen Datenverlust zu vermeiden", "user_import_bad_file": "Deine CSV-Datei ist nicht korrekt formatiert und wird daher ignoriert, um einen möglichen Datenverlust zu vermeiden",
"global_settings_setting_security_experimental_enabled": "Aktiviere experimentelle Sicherheitsfunktionen (nur aktivieren, wenn Du weißt was Du tust!)",
"global_settings_setting_security_nginx_redirect_to_https": "HTTP-Anfragen standardmäßig auf HTTPs umleiten (NICHT AUSSCHALTEN, sofern Du nicht weißt was Du tust!)",
"user_import_missing_columns": "Die folgenden Spalten fehlen: {columns}", "user_import_missing_columns": "Die folgenden Spalten fehlen: {columns}",
"user_import_nothing_to_do": "Es muss kein Konto importiert werden", "user_import_nothing_to_do": "Es muss kein Konto importiert werden",
"user_import_partial_failed": "Der Import von Konten ist teilweise fehlgeschlagen", "user_import_partial_failed": "Der Import von Konten ist teilweise fehlgeschlagen",
@ -672,7 +657,6 @@
"migration_0021_modified_files": "Bitte beachte, dass die folgenden Dateien manuell geändert wurden und nach dem Update möglicherweise überschrieben werden: {manually_modified_files}", "migration_0021_modified_files": "Bitte beachte, dass die folgenden Dateien manuell geändert wurden und nach dem Update möglicherweise überschrieben werden: {manually_modified_files}",
"migration_0021_cleaning_up": "Bereinigung von Cache und Paketen nicht mehr nötig...", "migration_0021_cleaning_up": "Bereinigung von Cache und Paketen nicht mehr nötig...",
"migration_0021_patch_yunohost_conflicts": "Patch anwenden, um das Konfliktproblem zu umgehen...", "migration_0021_patch_yunohost_conflicts": "Patch anwenden, um das Konfliktproblem zu umgehen...",
"global_settings_setting_security_ssh_password_authentication": "Passwort-Authentifizierung für SSH zulassen",
"migration_description_0021_migrate_to_bullseye": "Upgrade des Systems auf Debian Bullseye und YunoHost 11.x", "migration_description_0021_migrate_to_bullseye": "Upgrade des Systems auf Debian Bullseye und YunoHost 11.x",
"migration_0021_general_warning": "Bitte beachte, dass diese Migration ein heikler Vorgang ist. Das YunoHost-Team hat sein Bestes getan, um sie zu überprüfen und zu testen, aber die Migration könnte immer noch Teile des Systems oder seiner Anwendungen beschädigen.\n\nEs wird daher empfohlen,:\n - Führe eine Sicherung aller kritischen Daten oder Applikationen durch. Mehr Informationen unter https://yunohost.org/backup;\n - Habe Geduld, nachdem du die Migration gestartet hast: Je nach Internetverbindung und Hardware kann es bis zu ein paar Stunden dauern, bis alles aktualisiert ist.", "migration_0021_general_warning": "Bitte beachte, dass diese Migration ein heikler Vorgang ist. Das YunoHost-Team hat sein Bestes getan, um sie zu überprüfen und zu testen, aber die Migration könnte immer noch Teile des Systems oder seiner Anwendungen beschädigen.\n\nEs wird daher empfohlen,:\n - Führe eine Sicherung aller kritischen Daten oder Applikationen durch. Mehr Informationen unter https://yunohost.org/backup;\n - Habe Geduld, nachdem du die Migration gestartet hast: Je nach Internetverbindung und Hardware kann es bis zu ein paar Stunden dauern, bis alles aktualisiert ist.",
"tools_upgrade": "Aktualisieren von Systempaketen", "tools_upgrade": "Aktualisieren von Systempaketen",
@ -683,5 +667,20 @@
"migration_description_0022_php73_to_php74_pools": "Migriere php7.3-fpm 'pool' Konfiguration nach php7.4", "migration_description_0022_php73_to_php74_pools": "Migriere php7.3-fpm 'pool' Konfiguration nach php7.4",
"migration_description_0023_postgresql_11_to_13": "Migrieren von Datenbanken von PostgreSQL 11 nach 13", "migration_description_0023_postgresql_11_to_13": "Migrieren von Datenbanken von PostgreSQL 11 nach 13",
"service_description_postgresql": "Speichert Applikations-Daten (SQL Datenbank)", "service_description_postgresql": "Speichert Applikations-Daten (SQL Datenbank)",
"migration_0023_not_enough_space": "Stelle sicher, dass unter {path} genug Speicherplatz zur Verfügung steht, um die Migration auszuführen." "migration_0023_not_enough_space": "Stelle sicher, dass unter {path} genug Speicherplatz zur Verfügung steht, um die Migration auszuführen.",
"global_settings_setting_backup_compress_tar_archives_help": "Beim Erstellen von Backups die Archive komprimieren (.tar.gz) anstelle von unkomprimierten Archiven (.tar). N.B. : Diese Option ergibt leichtere Backup-Archive, aber das initiale Backupprozedere wird länger dauern und mehr CPU brauchen.",
"global_settings_setting_security_experimental_enabled_help": "Aktiviere experimentelle Sicherheitsfunktionen (nur aktivieren, wenn Du weißt was Du tust!)",
"global_settings_setting_nginx_compatibility_help": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den Webserver NGINX. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)",
"global_settings_setting_nginx_redirect_to_https_help": "HTTP-Anfragen standardmäßig auf HTTPs umleiten (NICHT AUSSCHALTEN, sofern Du nicht weißt was Du tust!)",
"global_settings_setting_admin_strength": "Stärke des Admin-Passworts",
"global_settings_setting_user_strength": "Stärke des Anmeldepassworts",
"global_settings_setting_postfix_compatibility_help": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den Postfix-Server. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)",
"global_settings_setting_ssh_compatibility_help": "Kompatibilitäts- vs. Sicherheits-Kompromiss für den SSH-Server. Betrifft die Ciphers (und andere sicherheitsrelevante Aspekte)",
"global_settings_setting_ssh_password_authentication_help": "Passwort-Authentifizierung für SSH zulassen",
"global_settings_setting_ssh_port": "SSH-Port",
"global_settings_setting_webadmin_allowlist_help": "IP-Adressen, die auf die Verwaltungsseite zugreifen dürfen. Kommasepariert.",
"global_settings_setting_webadmin_allowlist_enabled_help": "Erlaube nur bestimmten IP-Adressen den Zugriff auf die Verwaltungsseite.",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "Erlaubt die Verwendung eines (veralteten) DSA-Hostkeys für die SSH-Daemon-Konfiguration",
"global_settings_setting_smtp_allow_ipv6_help": "Erlaube die Nutzung von IPv6 um Mails zu empfangen und zu versenden",
"global_settings_setting_smtp_relay_enabled_help": "Zu verwendender SMTP-Relay-Host um E-Mails zu versenden. Er wird anstelle dieser YunoHost-Instanz verwendet. Nützlich, wenn du in einer der folgenden Situationen bist: Dein ISP- oder VPS-Provider hat deinen Port 25 geblockt, eine deinen residentiellen IPs ist auf DUHL gelistet, du kannst keinen Reverse-DNS konfigurieren oder dieser Server ist nicht direkt mit dem Internet verbunden und du möchtest einen anderen verwenden, um E-Mails zu versenden."
} }

View file

@ -4,12 +4,12 @@
"additional_urls_already_added": "Additionnal URL '{url}' already added in the additional URL for permission '{permission}'", "additional_urls_already_added": "Additionnal URL '{url}' already added in the additional URL for permission '{permission}'",
"additional_urls_already_removed": "Additionnal URL '{url}' already removed in the additional URL for permission '{permission}'", "additional_urls_already_removed": "Additionnal URL '{url}' already removed in the additional URL for permission '{permission}'",
"admin_password": "Administration password", "admin_password": "Administration password",
"admin_password_change_failed": "Unable to change password", "admins": "Admins",
"admin_password_changed": "The administration password was changed", "all_users": "All YunoHost users",
"admin_password_too_long": "Please choose a password shorter than 127 characters",
"already_up_to_date": "Nothing to do. Everything is already up-to-date.", "already_up_to_date": "Nothing to do. Everything is already up-to-date.",
"app_action_broke_system": "This action seems to have broken these important services: {services}", "app_action_broke_system": "This action seems to have broken these important services: {services}",
"app_action_cannot_be_ran_because_required_services_down": "These required services should be running to run this action: {services}. Try restarting them to continue (and possibly investigate why they are down).", "app_action_cannot_be_ran_because_required_services_down": "These required services should be running to run this action: {services}. Try restarting them to continue (and possibly investigate why they are down).",
"app_action_failed": "Failed to run action {action} for app {app}",
"app_already_installed": "{app} is already installed", "app_already_installed": "{app} is already installed",
"app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Check in `app changeurl` if it's available.", "app_already_installed_cant_change_url": "This app is already installed. The URL cannot be changed just by this function. Check in `app changeurl` if it's available.",
"app_already_up_to_date": "{app} is already up-to-date", "app_already_up_to_date": "{app} is already up-to-date",
@ -33,6 +33,8 @@
"app_make_default_location_already_used": "Unable to make '{app}' the default app on the domain, '{domain}' is already in use by '{other_app}'", "app_make_default_location_already_used": "Unable to make '{app}' the default app on the domain, '{domain}' is already in use by '{other_app}'",
"app_manifest_install_ask_admin": "Choose an administrator user for this app", "app_manifest_install_ask_admin": "Choose an administrator user for this app",
"app_manifest_install_ask_domain": "Choose the domain where this app should be installed", "app_manifest_install_ask_domain": "Choose the domain where this app should be installed",
"app_manifest_install_ask_init_admin_permission": "Who should have access to admin features for this app? (This can later be changed)",
"app_manifest_install_ask_init_main_permission": "Who should have access to this app? (This can later be changed)",
"app_manifest_install_ask_is_public": "Should this app be exposed to anonymous visitors?", "app_manifest_install_ask_is_public": "Should this app be exposed to anonymous visitors?",
"app_manifest_install_ask_password": "Choose an administration password for this app", "app_manifest_install_ask_password": "Choose an administration password for this app",
"app_manifest_install_ask_path": "Choose the URL path (after the domain) where this app should be installed", "app_manifest_install_ask_path": "Choose the URL path (after the domain) where this app should be installed",
@ -43,8 +45,7 @@
"app_packaging_format_not_supported": "This app cannot be installed because its packaging format is not supported by your YunoHost version. You should probably consider upgrading your system.", "app_packaging_format_not_supported": "This app cannot be installed because its packaging format is not supported by your YunoHost version. You should probably consider upgrading your system.",
"app_remove_after_failed_install": "Removing the app after installation failure...", "app_remove_after_failed_install": "Removing the app after installation failure...",
"app_removed": "{app} uninstalled", "app_removed": "{app} uninstalled",
"app_requirements_checking": "Checking required packages for {app}...", "app_requirements_checking": "Checking requirements for {app}...",
"app_requirements_unmeet": "Requirements are not met for {app}, the package {pkgname} ({version}) must be {spec}",
"app_restore_failed": "Could not restore {app}: {error}", "app_restore_failed": "Could not restore {app}: {error}",
"app_restore_script_failed": "An error occured inside the app restore script", "app_restore_script_failed": "An error occured inside the app restore script",
"app_sources_fetch_failed": "Could not fetch source files, is the URL correct?", "app_sources_fetch_failed": "Could not fetch source files, is the URL correct?",
@ -66,8 +67,9 @@
"apps_catalog_obsolete_cache": "The app catalog cache is empty or obsolete.", "apps_catalog_obsolete_cache": "The app catalog cache is empty or obsolete.",
"apps_catalog_update_success": "The application catalog has been updated!", "apps_catalog_update_success": "The application catalog has been updated!",
"apps_catalog_updating": "Updating application catalog...", "apps_catalog_updating": "Updating application catalog...",
"ask_firstname": "First name", "ask_admin_fullname": "Admin full name",
"ask_lastname": "Last name", "ask_admin_username": "Admin username",
"ask_fullname": "Full name",
"ask_main_domain": "Main domain", "ask_main_domain": "Main domain",
"ask_new_admin_password": "New administration password", "ask_new_admin_password": "New administration password",
"ask_new_domain": "New domain", "ask_new_domain": "New domain",
@ -125,8 +127,11 @@
"certmanager_attempt_to_renew_valid_cert": "The certificate for the domain '{domain}' is not about to expire! (You may use --force if you know what you're doing)", "certmanager_attempt_to_renew_valid_cert": "The certificate for the domain '{domain}' is not about to expire! (You may use --force if you know what you're doing)",
"certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain}! (Use --force to bypass)", "certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain}! (Use --force to bypass)",
"certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain} (file: {file}), reason: {reason}", "certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain} (file: {file}), reason: {reason}",
"certmanager_cert_install_failed": "Let's Encrypt certificate install failed for {domains}",
"certmanager_cert_install_failed_selfsigned": "Self-signed certificate install failed for {domains}",
"certmanager_cert_install_success": "Let's Encrypt certificate now installed for the domain '{domain}'", "certmanager_cert_install_success": "Let's Encrypt certificate now installed for the domain '{domain}'",
"certmanager_cert_install_success_selfsigned": "Self-signed certificate now installed for the domain '{domain}'", "certmanager_cert_install_success_selfsigned": "Self-signed certificate now installed for the domain '{domain}'",
"certmanager_cert_renew_failed": "Let's Encrypt certificate renew failed for {domains}",
"certmanager_cert_renew_success": "Let's Encrypt certificate renewed for the domain '{domain}'", "certmanager_cert_renew_success": "Let's Encrypt certificate renewed for the domain '{domain}'",
"certmanager_cert_signing_failed": "Could not sign the new certificate", "certmanager_cert_signing_failed": "Could not sign the new certificate",
"certmanager_certificate_fetching_or_enabling_failed": "Trying to use the new certificate for {domain} did not work...", "certmanager_certificate_fetching_or_enabling_failed": "Trying to use the new certificate for {domain} did not work...",
@ -139,9 +144,12 @@
"certmanager_self_ca_conf_file_not_found": "Could not find configuration file for self-signing authority (file: {file})", "certmanager_self_ca_conf_file_not_found": "Could not find configuration file for self-signing authority (file: {file})",
"certmanager_unable_to_parse_self_CA_name": "Could not parse name of self-signing authority (file: {file})", "certmanager_unable_to_parse_self_CA_name": "Could not parse name of self-signing authority (file: {file})",
"certmanager_warning_subdomain_dns_record": "Subdomain '{subdomain}' does not resolve to the same IP address as '{domain}'. Some features will not be available until you fix this and regenerate the certificate.", "certmanager_warning_subdomain_dns_record": "Subdomain '{subdomain}' does not resolve to the same IP address as '{domain}'. Some features will not be available until you fix this and regenerate the certificate.",
"config_action_disabled": "Could not run action '{action}' since it is disabled, make sure to meet its constraints. help: {help}",
"config_action_failed": "Failed to run action '{action}': {error}",
"config_apply_failed": "Applying the new configuration failed: {error}", "config_apply_failed": "Applying the new configuration failed: {error}",
"config_cant_set_value_on_section": "You can't set a single value on an entire config section.", "config_cant_set_value_on_section": "You can't set a single value on an entire config section.",
"config_forbidden_keyword": "The keyword '{keyword}' is reserved, you can't create or use a config panel with a question with this id.", "config_forbidden_keyword": "The keyword '{keyword}' is reserved, you can't create or use a config panel with a question with this id.",
"config_forbidden_readonly_type": "The type '{type}' can't be set as readonly, use another type to render this value (relevant arg id: '{id}').",
"config_no_panel": "No config panel found.", "config_no_panel": "No config panel found.",
"config_unknown_filter_key": "The filter key '{filter_key}' is incorrect.", "config_unknown_filter_key": "The filter key '{filter_key}' is incorrect.",
"config_validate_color": "Should be a valid RGB hexadecimal color", "config_validate_color": "Should be a valid RGB hexadecimal color",
@ -250,8 +258,8 @@
"diagnosis_mail_fcrdns_different_from_ehlo_domain": "Reverse DNS is not correctly configured for IPv{ipversion}. Some emails may fail to get delivered or be flagged as spam.", "diagnosis_mail_fcrdns_different_from_ehlo_domain": "Reverse DNS is not correctly configured for IPv{ipversion}. Some emails may fail to get delivered or be flagged as spam.",
"diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Current reverse DNS: <code>{rdns_domain}</code><br>Expected value: <code>{ehlo_domain}</code>", "diagnosis_mail_fcrdns_different_from_ehlo_domain_details": "Current reverse DNS: <code>{rdns_domain}</code><br>Expected value: <code>{ehlo_domain}</code>",
"diagnosis_mail_fcrdns_dns_missing": "No reverse DNS is defined in IPv{ipversion}. Some emails may fail to get delivered or be flagged as spam.", "diagnosis_mail_fcrdns_dns_missing": "No reverse DNS is defined in IPv{ipversion}. Some emails may fail to get delivered or be flagged as spam.",
"diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If you are experiencing issues because of this, consider the following solutions:<br> - Some ISP provide the alternative of <a href='https://yunohost.org/#/email_configure_relay'>using a mail server relay</a> though it implies that the relay will be able to spy on your email traffic.<br>- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass these kinds of limits. See <a href='https://yunohost.org/#/vpn_advantage'>https://yunohost.org/#/vpn_advantage</a><br>- Or it's possible to <a href='https://yunohost.org/#/isp'>switch to a different provider</a>", "diagnosis_mail_fcrdns_nok_alternatives_4": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If you are experiencing issues because of this, consider the following solutions:<br> - Some ISP provide the alternative of <a href='https://yunohost.org/#/email_configure_relay'>using a mail server relay</a> though it implies that the relay will be able to spy on your email traffic.<br>- A privacy-friendly alternative is to use a VPN *with a dedicated public IP* to bypass this kind of limits. See <a href='https://yunohost.org/#/vpn_advantage'>https://yunohost.org/#/vpn_advantage</a><br>- Or it's possible to <a href='https://yunohost.org/#/isp'>switch to a different provider</a>",
"diagnosis_mail_fcrdns_nok_alternatives_6": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If your reverse DNS is correctly configured for IPv4, you can try disabling the use of IPv6 when sending emails by running <cmd>yunohost settings set smtp.allow_ipv6 -v off</cmd>. Note: this last solution means that you won't be able to send or receive emails from the few IPv6-only servers out there.", "diagnosis_mail_fcrdns_nok_alternatives_6": "Some providers won't let you configure your reverse DNS (or their feature might be broken...). If your reverse DNS is correctly configured for IPv4, you can try disabling the use of IPv6 when sending emails by running <cmd>yunohost settings set email.smtp.smtp_allow_ipv6 -v off</cmd>. Note: this last solution means that you won't be able to send or receive emails from the few IPv6-only servers out there.",
"diagnosis_mail_fcrdns_nok_details": "You should first try to configure reverse DNS with <code>{ehlo_domain}</code> in your internet router interface or your hosting provider interface. (Some hosting providers may require you to send them a support ticket for this).", "diagnosis_mail_fcrdns_nok_details": "You should first try to configure reverse DNS with <code>{ehlo_domain}</code> in your internet router interface or your hosting provider interface. (Some hosting providers may require you to send them a support ticket for this).",
"diagnosis_mail_fcrdns_ok": "Your reverse DNS is correctly configured!", "diagnosis_mail_fcrdns_ok": "Your reverse DNS is correctly configured!",
"diagnosis_mail_outgoing_port_25_blocked": "The SMTP mail server cannot send emails to other servers because outgoing port 25 is blocked in IPv{ipversion}.", "diagnosis_mail_outgoing_port_25_blocked": "The SMTP mail server cannot send emails to other servers because outgoing port 25 is blocked in IPv{ipversion}.",
@ -288,8 +296,8 @@
"diagnosis_services_bad_status_tip": "You can try to <a href='#/services/{service}'>restart the service</a>, and if it doesn't work, have a look at <a href='#/services/{service}'>the service logs in the webadmin</a> (from the command line, you can do this with <cmd>yunohost service restart {service}</cmd> and <cmd>yunohost service log {service}</cmd>).", "diagnosis_services_bad_status_tip": "You can try to <a href='#/services/{service}'>restart the service</a>, and if it doesn't work, have a look at <a href='#/services/{service}'>the service logs in the webadmin</a> (from the command line, you can do this with <cmd>yunohost service restart {service}</cmd> and <cmd>yunohost service log {service}</cmd>).",
"diagnosis_services_conf_broken": "Configuration is broken for service {service}!", "diagnosis_services_conf_broken": "Configuration is broken for service {service}!",
"diagnosis_services_running": "Service {service} is running!", "diagnosis_services_running": "Service {service} is running!",
"diagnosis_sshd_config_inconsistent": "It looks like the SSH port was manually modified in /etc/ssh/sshd_config. Since YunoHost 4.2, a new global setting 'security.ssh.port' is available to avoid manually editing the configuration.", "diagnosis_sshd_config_inconsistent": "It looks like the SSH port was manually modified in /etc/ssh/sshd_config. Since YunoHost 4.2, a new global setting 'security.ssh.ssh_port' is available to avoid manually editing the configuration.",
"diagnosis_sshd_config_inconsistent_details": "Please run <cmd>yunohost settings set security.ssh.port -v YOUR_SSH_PORT</cmd> to define the SSH port, and check <cmd>yunohost tools regen-conf ssh --dry-run --with-diff</cmd> and <cmd>yunohost tools regen-conf ssh --force</cmd> to reset your conf to the YunoHost recommendation.", "diagnosis_sshd_config_inconsistent_details": "Please run <cmd>yunohost settings set security.ssh.ssh_port -v YOUR_SSH_PORT</cmd> to define the SSH port, and check <cmd>yunohost tools regen-conf ssh --dry-run --with-diff</cmd> and <cmd>yunohost tools regen-conf ssh --force</cmd> to reset your conf to the YunoHost recommendation.",
"diagnosis_sshd_config_insecure": "The SSH configuration appears to have been manually modified, and is insecure because it contains no 'AllowGroups' or 'AllowUsers' directive to limit access to authorized users.", "diagnosis_sshd_config_insecure": "The SSH configuration appears to have been manually modified, and is insecure because it contains no 'AllowGroups' or 'AllowUsers' directive to limit access to authorized users.",
"diagnosis_swap_none": "The system has no swap at all. You should consider adding at least {recommended} of swap to avoid situations where the system runs out of memory.", "diagnosis_swap_none": "The system has no swap at all. You should consider adding at least {recommended} of swap to avoid situations where the system runs out of memory.",
"diagnosis_swap_notsomuch": "The system has only {total} swap. You should consider having at least {recommended} to avoid situations where the system runs out of memory.", "diagnosis_swap_notsomuch": "The system has only {total} swap. You should consider having at least {recommended} to avoid situations where the system runs out of memory.",
@ -306,6 +314,8 @@
"domain_cannot_remove_main": "You cannot remove '{domain}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n <another-domain>'; here is the list of candidate domains: {other_domains}", "domain_cannot_remove_main": "You cannot remove '{domain}' since it's the main domain, you first need to set another domain as the main domain using 'yunohost domain main-domain -n <another-domain>'; here is the list of candidate domains: {other_domains}",
"domain_cannot_remove_main_add_new_one": "You cannot remove '{domain}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add <another-domain.com>', then set is as the main domain using 'yunohost domain main-domain -n <another-domain.com>' and then you can remove the domain '{domain}' using 'yunohost domain remove {domain}'.'", "domain_cannot_remove_main_add_new_one": "You cannot remove '{domain}' since it's the main domain and your only domain, you need to first add another domain using 'yunohost domain add <another-domain.com>', then set is as the main domain using 'yunohost domain main-domain -n <another-domain.com>' and then you can remove the domain '{domain}' using 'yunohost domain remove {domain}'.'",
"domain_cert_gen_failed": "Could not generate certificate", "domain_cert_gen_failed": "Could not generate certificate",
"domain_config_acme_eligible": "ACME eligibility",
"domain_config_acme_eligible_explain": "This domain doesn't seem ready for a Let's Encrypt certificate. Please check your DNS configuration and HTTP server reachability. The 'DNS records' and 'Web' section in <a href='#/diagnosis'>the diagnosis page</a> can help you understand what is misconfigured.",
"domain_config_api_protocol": "API protocol", "domain_config_api_protocol": "API protocol",
"domain_config_auth_application_key": "Application key", "domain_config_auth_application_key": "Application key",
"domain_config_auth_application_secret": "Application secret key", "domain_config_auth_application_secret": "Application secret key",
@ -314,6 +324,18 @@
"domain_config_auth_key": "Authentication key", "domain_config_auth_key": "Authentication key",
"domain_config_auth_secret": "Authentication secret", "domain_config_auth_secret": "Authentication secret",
"domain_config_auth_token": "Authentication token", "domain_config_auth_token": "Authentication token",
"domain_config_cert_install": "Install Let's Encrypt certificate",
"domain_config_cert_issuer": "Certification authority",
"domain_config_cert_no_checks": "Ignore diagnosis checks",
"domain_config_cert_renew": "Renew Let's Encrypt certificate",
"domain_config_cert_renew_help": "Certificate will be automatically renewed during the last 15 days of validity. You can manually renew it if you want to. (Not recommended).",
"domain_config_cert_summary": "Certificate status",
"domain_config_cert_summary_abouttoexpire": "Current certificate is about to expire. It should soon be renewed automatically.",
"domain_config_cert_summary_expired": "CRITICAL: Current certificate is not valid! HTTPS won't work at all!",
"domain_config_cert_summary_letsencrypt": "Great! You're using a valid Let's Encrypt certificate!",
"domain_config_cert_summary_ok": "Okay, current certificate looks good!",
"domain_config_cert_summary_selfsigned": "WARNING: Current certificate is self-signed. Browsers will display a spooky warning to new visitors!",
"domain_config_cert_validity": "Validity",
"domain_config_default_app": "Default app", "domain_config_default_app": "Default app",
"domain_config_features_disclaimer": "So far, enabling/disabling mail or XMPP features only impact the recommended and automatic DNS configuration, not system configurations!", "domain_config_features_disclaimer": "So far, enabling/disabling mail or XMPP features only impact the recommended and automatic DNS configuration, not system configurations!",
"domain_config_mail_in": "Incoming emails", "domain_config_mail_in": "Incoming emails",
@ -364,42 +386,50 @@
"dyndns_registered": "DynDNS domain registered", "dyndns_registered": "DynDNS domain registered",
"dyndns_registration_failed": "Could not register DynDNS domain: {error}", "dyndns_registration_failed": "Could not register DynDNS domain: {error}",
"dyndns_unavailable": "The domain '{domain}' is unavailable.", "dyndns_unavailable": "The domain '{domain}' is unavailable.",
"experimental_feature": "Warning: This feature is experimental and not considered stable, you should not use it unless you know what you are doing.",
"extracting": "Extracting...", "extracting": "Extracting...",
"field_invalid": "Invalid field '{}'", "field_invalid": "Invalid field '{}'",
"file_does_not_exist": "The file {path} does not exist.", "file_does_not_exist": "The file {path} does not exist.",
"firewall_reload_failed": "Could not reload the firewall", "firewall_reload_failed": "Could not reload the firewall",
"firewall_reloaded": "Firewall reloaded", "firewall_reloaded": "Firewall reloaded",
"firewall_rules_cmd_failed": "Some firewall rule commands have failed. More info in log.", "firewall_rules_cmd_failed": "Some firewall rule commands have failed. More info in log.",
"global_settings_bad_choice_for_enum": "Bad choice for setting {setting}, received '{choice}', but available choices are: {available_choices}", "global_settings_reset_success": "Reset global settings",
"global_settings_bad_type_for_setting": "Bad type for setting {setting}, received {received_type}, expected {expected_type}", "global_settings_setting_admin_strength": "Admin password strength requirements",
"global_settings_cant_open_settings": "Could not open settings file, reason: {reason}", "global_settings_setting_admin_strength_help": "These requirements are only enforced when initializing or changing the password",
"global_settings_cant_serialize_settings": "Could not serialize settings data, reason: {reason}", "global_settings_setting_backup_compress_tar_archives": "Compress backups",
"global_settings_cant_write_settings": "Could not save settings file, reason: {reason}", "global_settings_setting_backup_compress_tar_archives_help": "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_key_doesnt_exists": "The key '{settings_key}' does not exist in the global settings, you can see all the available keys by running 'yunohost settings list'", "global_settings_setting_nginx_compatibility": "NGINX Compatibility",
"global_settings_reset_success": "Previous settings now backed up to {path}", "global_settings_setting_nginx_compatibility_help": "Compatibility vs. security tradeoff for the web server NGINX. Affects the ciphers (and other security-related aspects)",
"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_setting_nginx_redirect_to_https": "Force HTTPS",
"global_settings_setting_pop3_enabled": "Enable the POP3 protocol for the mail server", "global_settings_setting_nginx_redirect_to_https_help": "Redirect HTTP requests to HTTPs by default (DO NOT TURN OFF unless you really know what you're doing!)",
"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_pop3_enabled": "Enable POP3",
"global_settings_setting_security_nginx_compatibility": "Compatibility vs. security tradeoff for the web server NGINX. Affects the ciphers (and other security-related aspects)", "global_settings_setting_pop3_enabled_help": "Enable the POP3 protocol for the mail server",
"global_settings_setting_security_nginx_redirect_to_https": "Redirect HTTP requests to HTTPs by default (DO NOT TURN OFF unless you really know what you're doing!)", "global_settings_setting_postfix_compatibility": "Postfix Compatibility",
"global_settings_setting_security_password_admin_strength": "Admin password strength", "global_settings_setting_postfix_compatibility_help": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)",
"global_settings_setting_security_password_user_strength": "User password strength", "global_settings_setting_root_access_explain": "On Linux systems, 'root' is the absolute admin. In YunoHost context, direct 'root' SSH login is by default disable - except from the local network of the server. Members of the 'admins' group can use the sudo command to act as root from the command line. However, it can be helpful to have a (robust) root password to debug the system if for some reason regular admins can not login anymore.",
"global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)", "global_settings_setting_root_password": "New root password",
"global_settings_setting_security_ssh_compatibility": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)", "global_settings_setting_root_password_confirm": "New root password (confirm)",
"global_settings_setting_security_ssh_password_authentication": "Allow password authentication for SSH", "global_settings_setting_security_experimental_enabled": "Experimental security features",
"global_settings_setting_security_ssh_port": "SSH port", "global_settings_setting_security_experimental_enabled_help": "Enable experimental security features (don't enable this if you don't know what you're doing!)",
"global_settings_setting_security_webadmin_allowlist": "IP adresses allowed to access the webadmin. Comma-separated.", "global_settings_setting_smtp_allow_ipv6": "Allow IPv6",
"global_settings_setting_security_webadmin_allowlist_enabled": "Allow only some IPs to access the webadmin.", "global_settings_setting_smtp_allow_ipv6_help": "Allow the use of IPv6 to receive and send mail",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Allow the use of (deprecated) DSA hostkey for the SSH daemon configuration", "global_settings_setting_smtp_relay_enabled": "Enable SMTP relay",
"global_settings_setting_smtp_allow_ipv6": "Allow the use of IPv6 to receive and send mail", "global_settings_setting_smtp_relay_enabled_help": "Enable the SMTP relay to use in order to send mail instead of this yunohost instance. Useful if you are in one of this situation: your 25 port is blocked by your ISP or VPS provider, you have a residential IP listed on DUHL, you are not able to configure reverse DNS or this server is not directly exposed on the internet and you want use an other one to send mails.",
"global_settings_setting_smtp_relay_host": "SMTP relay host to use in order to send mail instead of this yunohost instance. Useful if you are in one of this situation: your 25 port is blocked by your ISP or VPS provider, you have a residential IP listed on DUHL, you are not able to configure reverse DNS or this server is not directly exposed on the internet and you want use an other one to send mails.", "global_settings_setting_smtp_relay_host": "SMTP relay host",
"global_settings_setting_smtp_relay_password": "SMTP relay host password", "global_settings_setting_smtp_relay_password": "SMTP relay password",
"global_settings_setting_smtp_relay_port": "SMTP relay port", "global_settings_setting_smtp_relay_port": "SMTP relay port",
"global_settings_setting_smtp_relay_user": "SMTP relay user account", "global_settings_setting_smtp_relay_user": "SMTP relay user",
"global_settings_setting_ssowat_panel_overlay_enabled": "Enable SSOwat panel overlay", "global_settings_setting_ssh_compatibility": "SSH Compatibility",
"global_settings_unknown_setting_from_settings_file": "Unknown key in settings: '{setting_key}', discard it and save it in /etc/yunohost/settings-unknown.json", "global_settings_setting_ssh_compatibility_help": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects). See https://infosec.mozilla.org/guidelines/openssh for more info.",
"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.", "global_settings_setting_ssh_password_authentication": "Password authentication",
"global_settings_setting_ssh_password_authentication_help": "Allow password authentication for SSH",
"global_settings_setting_ssh_port": "SSH port",
"global_settings_setting_ssowat_panel_overlay_enabled": "Enable the small 'YunoHost' portal shortcut square on apps",
"global_settings_setting_user_strength": "User password strength requirements",
"global_settings_setting_user_strength_help": "These requirements are only enforced when initializing or changing the password",
"global_settings_setting_webadmin_allowlist": "Webadmin IP allowlist",
"global_settings_setting_webadmin_allowlist_enabled": "Enable Webadmin IP allowlist",
"global_settings_setting_webadmin_allowlist_enabled_help": "Allow only some IPs to access the webadmin.",
"global_settings_setting_webadmin_allowlist_help": "IP adresses allowed to access the webadmin.",
"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).", "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).",
"good_practices_about_user_password": "You are now about to define a new user 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 a variation of characters (uppercase, lowercase, digits and special characters).", "good_practices_about_user_password": "You are now about to define a new user 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 a variation of characters (uppercase, lowercase, digits and special characters).",
"group_already_exist": "Group {group} already exists", "group_already_exist": "Group {group} already exists",
@ -424,10 +454,10 @@
"hook_list_by_invalid": "This property can not be used to list hooks", "hook_list_by_invalid": "This property can not be used to list hooks",
"hook_name_unknown": "Unknown hook name '{name}'", "hook_name_unknown": "Unknown hook name '{name}'",
"installation_complete": "Installation completed", "installation_complete": "Installation completed",
"invalid_credentials": "Invalid password or username",
"invalid_number": "Must be a number", "invalid_number": "Must be a number",
"invalid_number_max": "Must be lesser than {max}", "invalid_number_max": "Must be lesser than {max}",
"invalid_number_min": "Must be greater than {min}", "invalid_number_min": "Must be greater than {min}",
"invalid_password": "Invalid password",
"invalid_regex": "Invalid regex:'{regex}'", "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", "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", "iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it",
@ -467,7 +497,11 @@
"log_regen_conf": "Regenerate system configurations '{}'", "log_regen_conf": "Regenerate system configurations '{}'",
"log_remove_on_failed_install": "Remove '{}' after a failed installation", "log_remove_on_failed_install": "Remove '{}' after a failed installation",
"log_remove_on_failed_restore": "Remove '{}' after a failed restore from a backup archive", "log_remove_on_failed_restore": "Remove '{}' after a failed restore from a backup archive",
"log_resource_snippet": "Provisioning/deprovisioning/updating a resource",
"log_selfsigned_cert_install": "Install self-signed certificate on '{}' domain", "log_selfsigned_cert_install": "Install self-signed certificate on '{}' domain",
"log_settings_reset": "Reset setting",
"log_settings_reset_all": "Reset all settings",
"log_settings_set": "Apply settings",
"log_tools_migrations_migrate_forward": "Run migrations", "log_tools_migrations_migrate_forward": "Run migrations",
"log_tools_postinstall": "Postinstall your YunoHost server", "log_tools_postinstall": "Postinstall your YunoHost server",
"log_tools_reboot": "Reboot your server", "log_tools_reboot": "Reboot your server",
@ -516,6 +550,8 @@
"migration_description_0022_php73_to_php74_pools": "Migrate php7.3-fpm 'pool' conf files to php7.4", "migration_description_0022_php73_to_php74_pools": "Migrate php7.3-fpm 'pool' conf files to php7.4",
"migration_description_0023_postgresql_11_to_13": "Migrate databases from PostgreSQL 11 to 13", "migration_description_0023_postgresql_11_to_13": "Migrate databases from PostgreSQL 11 to 13",
"migration_description_0024_rebuild_python_venv": "Repair Python app after bullseye migration", "migration_description_0024_rebuild_python_venv": "Repair Python app after bullseye migration",
"migration_description_0025_global_settings_to_configpanel": "Migrate legacy global settings nomenclature to the new, modern nomenclature",
"migration_description_0026_new_admins_group": "Migrate to the new 'multiple admins' system",
"migration_ldap_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.", "migration_ldap_backup_before_migration": "Creating a backup of LDAP database and apps settings prior to the actual migration.",
"migration_ldap_can_not_backup_before_migration": "The backup of the system could not be completed before the migration failed. Error: {error}", "migration_ldap_can_not_backup_before_migration": "The backup of the system could not be completed before the migration failed. Error: {error}",
"migration_ldap_migration_failed_trying_to_rollback": "Could not migrate... trying to roll back the system.", "migration_ldap_migration_failed_trying_to_rollback": "Could not migrate... trying to roll back the system.",
@ -540,7 +576,9 @@
"not_enough_disk_space": "Not enough free space on '{path}'", "not_enough_disk_space": "Not enough free space on '{path}'",
"operation_interrupted": "The operation was manually interrupted?", "operation_interrupted": "The operation was manually interrupted?",
"other_available_options": "... and {n} other available options not shown", "other_available_options": "... and {n} other available options not shown",
"password_confirmation_not_the_same": "The password and its confirmation do not match",
"password_listed": "This password is among the most used passwords in the world. Please choose something more unique.", "password_listed": "This password is among the most used passwords in the world. Please choose something more unique.",
"password_too_long": "Please choose a password shorter than 127 characters",
"password_too_simple_1": "The password needs to be at least 8 characters long", "password_too_simple_1": "The password needs to be at least 8 characters long",
"password_too_simple_2": "The password needs to be at least 8 characters long and contain a digit, upper and lower characters", "password_too_simple_2": "The password needs to be at least 8 characters long and contain a digit, upper and lower characters",
"password_too_simple_3": "The password needs to be at least 8 characters long and contain a digit, upper, lower and special characters", "password_too_simple_3": "The password needs to be at least 8 characters long and contain a digit, upper, lower and special characters",
@ -549,8 +587,9 @@
"pattern_domain": "Must be a valid domain name (e.g. my-domain.org)", "pattern_domain": "Must be a valid domain name (e.g. my-domain.org)",
"pattern_email": "Must be a valid e-mail address, without '+' symbol (e.g. someone@example.com)", "pattern_email": "Must be a valid e-mail address, without '+' symbol (e.g. someone@example.com)",
"pattern_email_forward": "Must be a valid e-mail address, '+' symbol accepted (e.g. someone+tag@example.com)", "pattern_email_forward": "Must be a valid e-mail address, '+' symbol accepted (e.g. someone+tag@example.com)",
"pattern_firstname": "Must be a valid first name", "pattern_firstname": "Must be a valid first name (at least 3 chars)",
"pattern_lastname": "Must be a valid last name", "pattern_fullname": "Must be a valid full name (at least 3 chars)",
"pattern_lastname": "Must be a valid last name (at least 3 chars)",
"pattern_mailbox_quota": "Must be a size with b/k/M/G/T suffix or 0 to not have a quota", "pattern_mailbox_quota": "Must be a size with b/k/M/G/T suffix or 0 to not have a quota",
"pattern_password": "Must be at least 3 characters long", "pattern_password": "Must be at least 3 characters long",
"pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}", "pattern_password_app": "Sorry, passwords can not contain the following characters: {forbidden_chars}",
@ -593,6 +632,7 @@
"regenconf_would_be_updated": "The configuration would have been updated for category '{category}'", "regenconf_would_be_updated": "The configuration would have been updated for category '{category}'",
"regex_incompatible_with_tile": "/!\\ Packagers! Permission '{permission}' has show_tile set to 'true' and you therefore cannot define a regex URL as the main URL", "regex_incompatible_with_tile": "/!\\ Packagers! Permission '{permission}' has show_tile set to 'true' and you therefore cannot define a regex URL as the main URL",
"regex_with_only_domain": "You can't use a regex for domain, only for path", "regex_with_only_domain": "You can't use a regex for domain, only for path",
"registrar_infos": "Registrar infos",
"restore_already_installed_app": "An app with the ID '{app}' is already installed", "restore_already_installed_app": "An app with the ID '{app}' is already installed",
"restore_already_installed_apps": "The following apps can't be restored because they are already installed: {apps}", "restore_already_installed_apps": "The following apps can't be restored because they are already installed: {apps}",
"restore_backup_too_old": "This backup archive can not be restored because it comes from a too-old YunoHost version.", "restore_backup_too_old": "This backup archive can not be restored because it comes from a too-old YunoHost version.",
@ -609,8 +649,8 @@
"restore_running_app_script": "Restoring the app '{app}'...", "restore_running_app_script": "Restoring the app '{app}'...",
"restore_running_hooks": "Running restoration hooks...", "restore_running_hooks": "Running restoration hooks...",
"restore_system_part_failed": "Could not restore the '{part}' system part", "restore_system_part_failed": "Could not restore the '{part}' system part",
"root_password_changed": "root's password was changed",
"root_password_desynchronized": "The admin password was changed, but YunoHost could not propagate this to the root password!", "root_password_desynchronized": "The admin password was changed, but YunoHost could not propagate this to the root password!",
"root_password_replaced_by_admin_password": "Your root password have been replaced by your admin password.",
"server_reboot": "The server will reboot", "server_reboot": "The server will reboot",
"server_reboot_confirm": "The server will reboot immediatly, are you sure? [{answers}]", "server_reboot_confirm": "The server will reboot immediatly, are you sure? [{answers}]",
"server_shutdown": "The server will shut down", "server_shutdown": "The server will shut down",
@ -691,9 +731,10 @@
"user_unknown": "Unknown user: {user}", "user_unknown": "Unknown user: {user}",
"user_update_failed": "Could not update user {user}: {error}", "user_update_failed": "Could not update user {user}: {error}",
"user_updated": "User info changed", "user_updated": "User info changed",
"visitors": "Visitors",
"yunohost_already_installed": "YunoHost is already installed", "yunohost_already_installed": "YunoHost is already installed",
"yunohost_configured": "YunoHost is now configured", "yunohost_configured": "YunoHost is now configured",
"yunohost_installing": "Installing YunoHost...", "yunohost_installing": "Installing YunoHost...",
"yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'", "yunohost_not_installed": "YunoHost is not correctly installed. Please run 'yunohost tools postinstall'",
"yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - adding a first user through the 'Users' section of the webadmin (or 'yunohost user create <username>' in command-line);\n - diagnose potential issues through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line);\n - reading the 'Finalizing your setup' and 'Getting to know YunoHost' parts in the admin documentation: https://yunohost.org/admindoc." "yunohost_postinstall_end_tip": "The post-install completed! To finalize your setup, please consider:\n - diagnose potential issues through the 'Diagnosis' section of the webadmin (or 'yunohost diagnosis run' in command-line);\n - reading the 'Finalizing your setup' and 'Getting to know YunoHost' parts in the admin documentation: https://yunohost.org/admindoc."
} }

View file

@ -105,7 +105,6 @@
"field_invalid": "Nevalida kampo '{}'", "field_invalid": "Nevalida kampo '{}'",
"log_app_makedefault": "Faru '{}' la defaŭlta apliko", "log_app_makedefault": "Faru '{}' la defaŭlta apliko",
"backup_system_part_failed": "Ne eblis sekurkopi la sistemon de '{part}'", "backup_system_part_failed": "Ne eblis sekurkopi la sistemon de '{part}'",
"global_settings_setting_security_postfix_compatibility": "Kongruo vs sekureca kompromiso por la Postfix-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)",
"group_unknown": "La grupo '{group}' estas nekonata", "group_unknown": "La grupo '{group}' estas nekonata",
"mailbox_disabled": "Retpoŝto malŝaltita por uzanto {user}", "mailbox_disabled": "Retpoŝto malŝaltita por uzanto {user}",
"migrations_dependencies_not_satisfied": "Rulu ĉi tiujn migradojn: '{dependencies_id}', antaŭ migrado {id}.", "migrations_dependencies_not_satisfied": "Rulu ĉi tiujn migradojn: '{dependencies_id}', antaŭ migrado {id}.",
@ -238,7 +237,6 @@
"dyndns_unavailable": "La domajno '{domain}' ne haveblas.", "dyndns_unavailable": "La domajno '{domain}' ne haveblas.",
"experimental_feature": "Averto: Ĉi tiu funkcio estas eksperimenta kaj ne konsiderata stabila, vi ne uzu ĝin krom se vi scias kion vi faras.", "experimental_feature": "Averto: Ĉi tiu funkcio estas eksperimenta kaj ne konsiderata stabila, vi ne uzu ĝin krom se vi scias kion vi faras.",
"root_password_replaced_by_admin_password": "Via radika pasvorto estis anstataŭigita per via administra pasvorto.", "root_password_replaced_by_admin_password": "Via radika pasvorto estis anstataŭigita per via administra pasvorto.",
"global_settings_setting_security_password_user_strength": "Uzanto pasvorta forto",
"restore_may_be_not_enough_disk_space": "Via sistemo ne ŝajnas havi sufiĉe da spaco (libera: {free_space} B, necesa spaco: {needed_space} B, sekureca marĝeno: {margin} B)", "restore_may_be_not_enough_disk_space": "Via sistemo ne ŝajnas havi sufiĉe da spaco (libera: {free_space} B, necesa spaco: {needed_space} B, sekureca marĝeno: {margin} B)",
"log_corrupted_md_file": "La YAD-metadata dosiero asociita kun protokoloj estas damaĝita: '{md_file}\nEraro: {error} '", "log_corrupted_md_file": "La YAD-metadata dosiero asociita kun protokoloj estas damaĝita: '{md_file}\nEraro: {error} '",
"downloading": "Elŝutante …", "downloading": "Elŝutante …",
@ -264,7 +262,6 @@
"log_user_delete": "Forigi uzanton '{}'", "log_user_delete": "Forigi uzanton '{}'",
"dyndns_ip_updated": "Ĝisdatigis vian IP sur DynDNS", "dyndns_ip_updated": "Ĝisdatigis vian IP sur DynDNS",
"regenconf_up_to_date": "La agordo jam estas ĝisdatigita por kategorio '{category}'", "regenconf_up_to_date": "La agordo jam estas ĝisdatigita por kategorio '{category}'",
"global_settings_setting_security_ssh_compatibility": "Kongruo vs sekureca kompromiso por la SSH-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)",
"migrations_need_to_accept_disclaimer": "Por funkciigi la migradon {id}, via devas akcepti la sekvan malakcepton:\n---\n{disclaimer}\n---\nSe vi akceptas funkcii la migradon, bonvolu rekonduki la komandon kun la opcio '--accept-disclaimer'.", "migrations_need_to_accept_disclaimer": "Por funkciigi la migradon {id}, via devas akcepti la sekvan malakcepton:\n---\n{disclaimer}\n---\nSe vi akceptas funkcii la migradon, bonvolu rekonduki la komandon kun la opcio '--accept-disclaimer'.",
"regenconf_file_remove_failed": "Ne povis forigi la agordodosieron '{conf}'", "regenconf_file_remove_failed": "Ne povis forigi la agordodosieron '{conf}'",
"not_enough_disk_space": "Ne sufiĉe libera spaco sur '{path}'", "not_enough_disk_space": "Ne sufiĉe libera spaco sur '{path}'",
@ -295,7 +292,6 @@
"log_backup_restore_system": "Restarigi sistemon de rezerva arkivo", "log_backup_restore_system": "Restarigi sistemon de rezerva arkivo",
"log_app_change_url": "Ŝanĝu la URL de la apliko '{}'", "log_app_change_url": "Ŝanĝu la URL de la apliko '{}'",
"service_already_started": "La servo '{service}' jam funkcias", "service_already_started": "La servo '{service}' jam funkcias",
"global_settings_setting_security_password_admin_strength": "Admin pasvorta forto",
"service_reload_or_restart_failed": "Ne povis reŝargi aŭ rekomenci la servon '{service}'\n\nLastatempaj servaj protokoloj: {logs}", "service_reload_or_restart_failed": "Ne povis reŝargi aŭ rekomenci la servon '{service}'\n\nLastatempaj servaj protokoloj: {logs}",
"migrations_list_conflict_pending_done": "Vi ne povas uzi ambaŭ '--previous' kaj '--done' samtempe.", "migrations_list_conflict_pending_done": "Vi ne povas uzi ambaŭ '--previous' kaj '--done' samtempe.",
"server_shutdown_confirm": "La servilo haltos tuj, ĉu vi certas? [{answers}]", "server_shutdown_confirm": "La servilo haltos tuj, ĉu vi certas? [{answers}]",
@ -310,7 +306,6 @@
"password_too_simple_4": "La pasvorto bezonas almenaŭ 12 signojn kaj enhavas ciferon, majuskle, pli malaltan kaj specialajn signojn", "password_too_simple_4": "La pasvorto bezonas almenaŭ 12 signojn kaj enhavas ciferon, majuskle, pli malaltan kaj specialajn signojn",
"regenconf_file_updated": "Agordodosiero '{conf}' ĝisdatigita", "regenconf_file_updated": "Agordodosiero '{conf}' ĝisdatigita",
"log_help_to_get_log": "Por vidi la protokolon de la operacio '{desc}', uzu la komandon 'yunohost log show {name}'", "log_help_to_get_log": "Por vidi la protokolon de la operacio '{desc}', uzu la komandon 'yunohost log show {name}'",
"global_settings_setting_security_nginx_compatibility": "Kongruo vs sekureca kompromiso por la TTT-servilo NGINX. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)",
"restore_complete": "Restarigita", "restore_complete": "Restarigita",
"hook_exec_failed": "Ne povis funkcii skripto: {path}", "hook_exec_failed": "Ne povis funkcii skripto: {path}",
"global_settings_cant_open_settings": "Ne eblis malfermi agordojn, tial: {reason}", "global_settings_cant_open_settings": "Ne eblis malfermi agordojn, tial: {reason}",
@ -352,7 +347,6 @@
"log_domain_remove": "Forigi domon '{}' de agordo de sistemo", "log_domain_remove": "Forigi domon '{}' de agordo de sistemo",
"hook_list_by_invalid": "Ĉi tiu posedaĵo ne povas esti uzata por listigi hokojn", "hook_list_by_invalid": "Ĉi tiu posedaĵo ne povas esti uzata por listigi hokojn",
"confirm_app_install_thirdparty": "Danĝero! Ĉi tiu apliko ne estas parto de la aplika katalogo de Yunohost. Instali triajn aplikojn povas kompromiti la integrecon kaj sekurecon de via sistemo. Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers}'", "confirm_app_install_thirdparty": "Danĝero! Ĉi tiu apliko ne estas parto de la aplika katalogo de Yunohost. Instali triajn aplikojn povas kompromiti la integrecon kaj sekurecon de via sistemo. Vi probable ne devas instali ĝin krom se vi scias kion vi faras. NENIU SUBTENO estos provizita se ĉi tiu app ne funkcias aŭ rompas vian sistemon ... Se vi pretas riski ĉiuokaze, tajpu '{answers}'",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permesu uzon de (malaktuala) DSA-hostkey por la agordo de daemon SSH",
"dyndns_domain_not_provided": "Provizanto DynDNS {provider} ne povas provizi domajnon {domain}.", "dyndns_domain_not_provided": "Provizanto DynDNS {provider} ne povas provizi domajnon {domain}.",
"backup_unable_to_organize_files": "Ne povis uzi la rapidan metodon por organizi dosierojn en la ar archiveivo", "backup_unable_to_organize_files": "Ne povis uzi la rapidan metodon por organizi dosierojn en la ar archiveivo",
"password_too_simple_2": "La pasvorto bezonas almenaŭ 8 signojn kaj enhavas ciferon, majusklojn kaj minusklojn", "password_too_simple_2": "La pasvorto bezonas almenaŭ 8 signojn kaj enhavas ciferon, majusklojn kaj minusklojn",
@ -454,7 +448,6 @@
"diagnosis_found_errors_and_warnings": "Trovis {errors} signifaj problemo (j) (kaj {warnings} averto) rilataj al {category}!", "diagnosis_found_errors_and_warnings": "Trovis {errors} signifaj problemo (j) (kaj {warnings} averto) rilataj al {category}!",
"diagnosis_diskusage_low": "Stokado <code>{mountpoint}</code> (sur aparato <code>{device}</code>) nur restas {free} ({free_percent}%) spaco restanta (el {total}). Estu zorgema.", "diagnosis_diskusage_low": "Stokado <code>{mountpoint}</code> (sur aparato <code>{device}</code>) nur restas {free} ({free_percent}%) spaco restanta (el {total}). Estu zorgema.",
"diagnosis_diskusage_ok": "Stokado <code>{mountpoint}</code> (sur aparato <code>{device}</code>) ankoraŭ restas {free} ({free_percent}%) spaco (el {total})!", "diagnosis_diskusage_ok": "Stokado <code>{mountpoint}</code> (sur aparato <code>{device}</code>) ankoraŭ restas {free} ({free_percent}%) spaco (el {total})!",
"global_settings_setting_pop3_enabled": "Ebligu la protokolon POP3 por la poŝta servilo",
"diagnosis_unknown_categories": "La jenaj kategorioj estas nekonataj: {categories}", "diagnosis_unknown_categories": "La jenaj kategorioj estas nekonataj: {categories}",
"diagnosis_services_running": "Servo {service} funkcias!", "diagnosis_services_running": "Servo {service} funkcias!",
"diagnosis_ports_unreachable": "Haveno {port} ne atingeblas de ekstere.", "diagnosis_ports_unreachable": "Haveno {port} ne atingeblas de ekstere.",
@ -516,7 +509,6 @@
"diagnosis_http_partially_unreachable": "Domajno {domain} ŝajnas neatingebla per HTTP de ekster la loka reto en IPv {failed}, kvankam ĝi funkcias en IPv {passed}.", "diagnosis_http_partially_unreachable": "Domajno {domain} ŝajnas neatingebla per HTTP de ekster la loka reto en IPv {failed}, kvankam ĝi funkcias en IPv {passed}.",
"diagnosis_http_nginx_conf_not_up_to_date": "La nginx-agordo de ĉi tiu domajno ŝajnas esti modifita permane, kaj malhelpas YunoHost diagnozi ĉu ĝi atingeblas per HTTP.", "diagnosis_http_nginx_conf_not_up_to_date": "La nginx-agordo de ĉi tiu domajno ŝajnas esti modifita permane, kaj malhelpas YunoHost diagnozi ĉu ĝi atingeblas per HTTP.",
"diagnosis_http_nginx_conf_not_up_to_date_details": "Por solvi la situacion, inspektu la diferencon per la komandlinio per <cmd>yunohost tools regen-conf nginx --dry-run --with-diff</cmd> kaj se vi aranĝas, apliku la ŝanĝojn per <cmd>yunohost tools regen-conf nginx --force</cmd>.", "diagnosis_http_nginx_conf_not_up_to_date_details": "Por solvi la situacion, inspektu la diferencon per la komandlinio per <cmd>yunohost tools regen-conf nginx --dry-run --with-diff</cmd> kaj se vi aranĝas, apliku la ŝanĝojn per <cmd>yunohost tools regen-conf nginx --force</cmd>.",
"global_settings_setting_smtp_allow_ipv6": "Permesu la uzon de IPv6 por ricevi kaj sendi poŝton",
"backup_archive_corrupted": "I aspektas kiel la rezerva arkivo '{archive}' estas koruptita: {error}", "backup_archive_corrupted": "I aspektas kiel la rezerva arkivo '{archive}' estas koruptita: {error}",
"backup_archive_cant_retrieve_info_json": "Ne povis ŝarĝi infos por arkivo '{archive}' ... la info.json ne povas esti reprenita (aŭ ne estas valida JSON).", "backup_archive_cant_retrieve_info_json": "Ne povis ŝarĝi infos por arkivo '{archive}' ... la info.json ne povas esti reprenita (aŭ ne estas valida JSON).",
"ask_user_domain": "Domajno uzi por la retpoŝta adreso de la uzanto kaj XMPP-konto", "ask_user_domain": "Domajno uzi por la retpoŝta adreso de la uzanto kaj XMPP-konto",
@ -530,5 +522,12 @@
"app_label_deprecated": "Ĉi tiu komando estas malrekomendita! Bonvolu uzi la novan komandon 'yunohost user permission update' por administri la app etikedo.", "app_label_deprecated": "Ĉi tiu komando estas malrekomendita! Bonvolu uzi la novan komandon 'yunohost user permission update' por administri la app etikedo.",
"app_argument_password_no_default": "Eraro dum analiza pasvorta argumento '{name}': pasvorta argumento ne povas havi defaŭltan valoron por sekureca kialo", "app_argument_password_no_default": "Eraro dum analiza pasvorta argumento '{name}': pasvorta argumento ne povas havi defaŭltan valoron por sekureca kialo",
"additional_urls_already_removed": "Plia URL '{url}' jam forigita en la aldona URL por permeso '{permission}'", "additional_urls_already_removed": "Plia URL '{url}' jam forigita en la aldona URL por permeso '{permission}'",
"additional_urls_already_added": "Plia URL '{url}' jam aldonita en la aldona URL por permeso '{permission}'" "additional_urls_already_added": "Plia URL '{url}' jam aldonita en la aldona URL por permeso '{permission}'",
"global_settings_setting_nginx_compatibility_help": "Kongruo vs sekureca kompromiso por la TTT-servilo NGINX. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)",
"global_settings_setting_admin_strength": "Admin pasvorta forto",
"global_settings_setting_user_strength": "Uzanto pasvorta forto",
"global_settings_setting_postfix_compatibility_help": "Kongruo vs sekureca kompromiso por la Postfix-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)",
"global_settings_setting_ssh_compatibility_help": "Kongruo vs sekureca kompromiso por la SSH-servilo. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "Permesu uzon de (malaktuala) DSA-hostkey por la agordo de daemon SSH",
"global_settings_setting_smtp_allow_ipv6_help": "Permesu la uzon de IPv6 por ricevi kaj sendi poŝton"
} }

View file

@ -320,13 +320,7 @@
"group_created": "Creado el grupo «{group}»", "group_created": "Creado el grupo «{group}»",
"good_practices_about_admin_password": "Ahora está a punto de definir una nueva contraseña de usuario. La contraseña debe tener al menos 8 caracteres, aunque es una buena práctica usar una contraseña más larga (es decir, una frase de contraseña) y / o una variación de caracteres (mayúsculas, minúsculas, dígitos y caracteres especiales).", "good_practices_about_admin_password": "Ahora está a punto de definir una nueva contraseña de usuario. La contraseña debe tener al menos 8 caracteres, aunque es una buena práctica usar una contraseña más larga (es decir, una frase de contraseña) y / o una variación de caracteres (mayúsculas, minúsculas, dígitos y caracteres especiales).",
"global_settings_unknown_type": "Situación imprevista, la configuración {setting} parece tener el tipo {unknown_type} pero no es un tipo compatible con el sistema.", "global_settings_unknown_type": "Situación imprevista, la configuración {setting} parece tener el tipo {unknown_type} pero no es un tipo compatible con el sistema.",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permitir el uso de la llave (obsoleta) DSA para la configuración del demonio SSH",
"global_settings_unknown_setting_from_settings_file": "Clave desconocida en la configuración: «{setting_key}», desechada y guardada en /etc/yunohost/settings-unknown.json", "global_settings_unknown_setting_from_settings_file": "Clave desconocida en la configuración: «{setting_key}», desechada y guardada en /etc/yunohost/settings-unknown.json",
"global_settings_setting_security_postfix_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor Postfix. Afecta al cifrado (y otros aspectos relacionados con la seguridad)",
"global_settings_setting_security_ssh_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor SSH. Afecta al cifrado (y otros aspectos relacionados con la seguridad)",
"global_settings_setting_security_password_user_strength": "Seguridad de la contraseña de usuario",
"global_settings_setting_security_password_admin_strength": "Seguridad de la contraseña del administrador",
"global_settings_setting_security_nginx_compatibility": "Compromiso entre compatibilidad y seguridad para el servidor web NGINX. Afecta al cifrado (y otros aspectos relacionados con la seguridad)",
"global_settings_reset_success": "Respaldada la configuración previa en {path}", "global_settings_reset_success": "Respaldada la configuración previa en {path}",
"global_settings_key_doesnt_exists": "La clave «{settings_key}» no existe en la configuración global, puede ver todas las claves disponibles ejecutando «yunohost settings list»", "global_settings_key_doesnt_exists": "La clave «{settings_key}» no existe en la configuración global, puede ver todas las claves disponibles ejecutando «yunohost settings list»",
"global_settings_cant_write_settings": "No se pudo guardar el archivo de configuración, motivo: {reason}", "global_settings_cant_write_settings": "No se pudo guardar el archivo de configuración, motivo: {reason}",
@ -464,7 +458,6 @@
"log_domain_main_domain": "Hacer de '{}' el dominio principal", "log_domain_main_domain": "Hacer de '{}' el dominio principal",
"log_app_action_run": "Inicializa la acción de la aplicación '{}'", "log_app_action_run": "Inicializa la acción de la aplicación '{}'",
"group_already_exist_on_system_but_removing_it": "El grupo {group} ya existe en los grupos del sistema, pero YunoHost lo suprimirá …", "group_already_exist_on_system_but_removing_it": "El grupo {group} ya existe en los grupos del sistema, pero YunoHost lo suprimirá …",
"global_settings_setting_pop3_enabled": "Habilita el protocolo POP3 para el servidor de correo electrónico",
"domain_cannot_remove_main_add_new_one": "No se puede remover '{domain}' porque es su principal y único dominio. Primero debe agregar un nuevo dominio con la linea de comando 'yunohost domain add <another-domain.com>', entonces configurarlo como dominio principal con 'yunohost domain main-domain -n <another-domain.com>' y finalmente borrar el dominio '{domain}' con 'yunohost domain remove {domain}'.'", "domain_cannot_remove_main_add_new_one": "No se puede remover '{domain}' porque es su principal y único dominio. Primero debe agregar un nuevo dominio con la linea de comando 'yunohost domain add <another-domain.com>', entonces configurarlo como dominio principal con 'yunohost domain main-domain -n <another-domain.com>' y finalmente borrar el dominio '{domain}' con 'yunohost domain remove {domain}'.'",
"diagnosis_never_ran_yet": "Este servidor todavía no tiene reportes de diagnostico. Puede iniciar un diagnostico completo desde la interface administrador web o con la linea de comando 'yunohost diagnosis run'.", "diagnosis_never_ran_yet": "Este servidor todavía no tiene reportes de diagnostico. Puede iniciar un diagnostico completo desde la interface administrador web o con la linea de comando 'yunohost diagnosis run'.",
"diagnosis_unknown_categories": "Las siguientes categorías están desconocidas: {categories}", "diagnosis_unknown_categories": "Las siguientes categorías están desconocidas: {categories}",
@ -510,12 +503,9 @@
"app_label_deprecated": "Este comando está depreciado! Favor usar el nuevo comando 'yunohost user permission update' para administrar la etiqueta de app.", "app_label_deprecated": "Este comando está depreciado! Favor usar el nuevo comando 'yunohost user permission update' para administrar la etiqueta de app.",
"app_argument_password_no_default": "Error al interpretar argumento de contraseña'{name}': El argumento de contraseña no puede tener un valor por defecto por razón de seguridad", "app_argument_password_no_default": "Error al interpretar argumento de contraseña'{name}': El argumento de contraseña no puede tener un valor por defecto por razón de seguridad",
"invalid_regex": "Regex no valido: «{regex}»", "invalid_regex": "Regex no valido: «{regex}»",
"global_settings_setting_backup_compress_tar_archives": "Cuando se creen nuevas copias de respaldo, comprimir los archivos (.tar.gz) en lugar de descomprimir los archivos (.tar). N.B.: activar esta opción quiere decir que los archivos serán más pequeños pero que el proceso tardará más y utilizará más CPU.",
"global_settings_setting_smtp_relay_password": "Clave de uso del SMTP", "global_settings_setting_smtp_relay_password": "Clave de uso del SMTP",
"global_settings_setting_smtp_relay_user": "Cuenta de uso de SMTP", "global_settings_setting_smtp_relay_user": "Cuenta de uso de SMTP",
"global_settings_setting_smtp_relay_port": "Puerto de envio / relay SMTP", "global_settings_setting_smtp_relay_port": "Puerto de envio / relay SMTP",
"global_settings_setting_smtp_relay_host": "El servidor relay de SMTP para enviar correo en lugar de esta instalación YunoHost. Útil si estás en una de estas situaciones: tu puerto 25 esta bloqueado por tu ISP o VPS, si estás en usado una IP marcada como residencial o DUHL, si no puedes configurar un DNS inverso o si el servidor no está directamente expuesto a internet y quieres utilizar otro servidor para enviar correos.",
"global_settings_setting_smtp_allow_ipv6": "Permitir el uso de IPv6 para enviar y recibir correo",
"diagnosis_processes_killed_by_oom_reaper": "Algunos procesos fueron terminados por el sistema recientemente porque se quedó sin memoria. Típicamente es sintoma de falta de memoria o de un proceso que se adjudicó demasiada memoria.<br>Resumen de los procesos terminados:<br>\n{kills_summary}", "diagnosis_processes_killed_by_oom_reaper": "Algunos procesos fueron terminados por el sistema recientemente porque se quedó sin memoria. Típicamente es sintoma de falta de memoria o de un proceso que se adjudicó demasiada memoria.<br>Resumen de los procesos terminados:<br>\n{kills_summary}",
"diagnosis_http_nginx_conf_not_up_to_date_details": "Para arreglar este asunto, estudia las diferencias mediante el comando <cmd>yunohost tools regen-conf nginx --dry-run --with-diff</cmd> y si te parecen bien aplica los cambios mediante <cmd>yunohost tools regen-conf nginx --force</cmd>.", "diagnosis_http_nginx_conf_not_up_to_date_details": "Para arreglar este asunto, estudia las diferencias mediante el comando <cmd>yunohost tools regen-conf nginx --dry-run --with-diff</cmd> y si te parecen bien aplica los cambios mediante <cmd>yunohost tools regen-conf nginx --force</cmd>.",
"diagnosis_http_nginx_conf_not_up_to_date": "Parece que la configuración nginx de este dominio haya sido modificada manualmente, esto no deja que YunoHost pueda diagnosticar si es accesible mediante HTTP.", "diagnosis_http_nginx_conf_not_up_to_date": "Parece que la configuración nginx de este dominio haya sido modificada manualmente, esto no deja que YunoHost pueda diagnosticar si es accesible mediante HTTP.",
@ -618,10 +608,7 @@
"domain_dns_registrar_managed_in_parent_domain": "Este dominio es un subdominio de {parent_domain_link}. La configuración del registrador de DNS debe administrarse en el panel de configuración de {parent_domain}.", "domain_dns_registrar_managed_in_parent_domain": "Este dominio es un subdominio de {parent_domain_link}. La configuración del registrador de DNS debe administrarse en el panel de configuración de {parent_domain}.",
"domain_dns_registrar_yunohost": "Este dominio es un nohost.me / nohost.st / ynh.fr y, por lo tanto, YunoHost maneja automáticamente su configuración de DNS sin ninguna configuración adicional. (vea el comando 'yunohost dyndns update')", "domain_dns_registrar_yunohost": "Este dominio es un nohost.me / nohost.st / ynh.fr y, por lo tanto, YunoHost maneja automáticamente su configuración de DNS sin ninguna configuración adicional. (vea el comando 'yunohost dyndns update')",
"domain_dns_registrar_not_supported": "YunoHost no pudo detectar automáticamente el registrador que maneja este dominio. Debe configurar manualmente sus registros DNS siguiendo la documentación en https://yunohost.org/dns.", "domain_dns_registrar_not_supported": "YunoHost no pudo detectar automáticamente el registrador que maneja este dominio. Debe configurar manualmente sus registros DNS siguiendo la documentación en https://yunohost.org/dns.",
"global_settings_setting_security_nginx_redirect_to_https": "Redirija las solicitudes HTTP a HTTPs de forma predeterminada (¡NO LO DESACTIVE a menos que realmente sepa lo que está haciendo!)",
"global_settings_setting_security_webadmin_allowlist": "Direcciones IP permitidas para acceder al webadmin. Separado por comas.",
"migration_ldap_backup_before_migration": "Creación de una copia de seguridad de la base de datos LDAP y la configuración de las aplicaciones antes de la migración real.", "migration_ldap_backup_before_migration": "Creación de una copia de seguridad de la base de datos LDAP y la configuración de las aplicaciones antes de la migración real.",
"global_settings_setting_security_ssh_port": "Puerto SSH",
"invalid_number": "Debe ser un miembro", "invalid_number": "Debe ser un miembro",
"ldap_server_is_down_restart_it": "El servicio LDAP está inactivo, intente reiniciarlo...", "ldap_server_is_down_restart_it": "El servicio LDAP está inactivo, intente reiniciarlo...",
"invalid_password": "Contraseña inválida", "invalid_password": "Contraseña inválida",
@ -644,7 +631,6 @@
"migration_0021_modified_files": "Tenga en cuenta que se encontró que los siguientes archivos se modificaron manualmente y podrían sobrescribirse después de la actualización: {manually_modified_files}", "migration_0021_modified_files": "Tenga en cuenta que se encontró que los siguientes archivos se modificaron manualmente y podrían sobrescribirse después de la actualización: {manually_modified_files}",
"invalid_number_min": "Debe ser mayor que {min}", "invalid_number_min": "Debe ser mayor que {min}",
"pattern_email_forward": "Debe ser una dirección de correo electrónico válida, se acepta el símbolo '+' (por ejemplo, alguien+etiqueta@ejemplo.com)", "pattern_email_forward": "Debe ser una dirección de correo electrónico válida, se acepta el símbolo '+' (por ejemplo, alguien+etiqueta@ejemplo.com)",
"global_settings_setting_security_ssh_password_authentication": "Permitir autenticación de contraseña para SSH",
"invalid_number_max": "Debe ser menor que {max}", "invalid_number_max": "Debe ser menor que {max}",
"ldap_attribute_already_exists": "El atributo LDAP '{attribute}' ya existe con el valor '{value}'", "ldap_attribute_already_exists": "El atributo LDAP '{attribute}' ya existe con el valor '{value}'",
"log_app_config_set": "Aplicar configuración a la aplicación '{}'", "log_app_config_set": "Aplicar configuración a la aplicación '{}'",
@ -656,8 +642,6 @@
"ldap_server_down": "No se puede conectar con el servidor LDAP", "ldap_server_down": "No se puede conectar con el servidor LDAP",
"log_backup_create": "Crear un archivo de copia de seguridad", "log_backup_create": "Crear un archivo de copia de seguridad",
"migration_ldap_can_not_backup_before_migration": "La copia de seguridad del sistema no se pudo completar antes de que fallara la migración. Error: {error}", "migration_ldap_can_not_backup_before_migration": "La copia de seguridad del sistema no se pudo completar antes de que fallara la migración. Error: {error}",
"global_settings_setting_security_experimental_enabled": "Habilite las funciones de seguridad experimentales (¡no habilite esto si no sabe lo que está haciendo!)",
"global_settings_setting_security_webadmin_allowlist_enabled": "Permita que solo algunas IP accedan al administrador web.",
"migration_ldap_migration_failed_trying_to_rollback": "No se pudo migrar... intentando revertir el sistema.", "migration_ldap_migration_failed_trying_to_rollback": "No se pudo migrar... intentando revertir el sistema.",
"migration_0023_not_enough_space": "Deje suficiente espacio disponible en {path} para ejecutar la migración.", "migration_0023_not_enough_space": "Deje suficiente espacio disponible en {path} para ejecutar la migración.",
"migration_0023_postgresql_11_not_installed": "PostgreSQL no estaba instalado en su sistema. Nada que hacer.", "migration_0023_postgresql_11_not_installed": "PostgreSQL no estaba instalado en su sistema. Nada que hacer.",
@ -683,5 +667,20 @@
"service_description_yunomdns": "Le permite llegar a su servidor usando 'yunohost.local' en su red local", "service_description_yunomdns": "Le permite llegar a su servidor usando 'yunohost.local' en su red local",
"show_tile_cant_be_enabled_for_regex": "No puede habilitar 'show_tile' en este momento porque la URL para el permiso '{permission}' es una expresión regular", "show_tile_cant_be_enabled_for_regex": "No puede habilitar 'show_tile' en este momento porque la URL para el permiso '{permission}' es una expresión regular",
"show_tile_cant_be_enabled_for_url_not_defined": "No puede habilitar 'show_tile' en este momento, porque primero debe definir una URL para el permiso '{permission}'", "show_tile_cant_be_enabled_for_url_not_defined": "No puede habilitar 'show_tile' en este momento, porque primero debe definir una URL para el permiso '{permission}'",
"regex_incompatible_with_tile": "/!\\ Empaquetadores! El permiso '{permission}' tiene show_tile establecido en 'true' y, por lo tanto, no puede definir una URL de expresión regular como la URL principal" "regex_incompatible_with_tile": "/!\\ Empaquetadores! El permiso '{permission}' tiene show_tile establecido en 'true' y, por lo tanto, no puede definir una URL de expresión regular como la URL principal",
"global_settings_setting_backup_compress_tar_archives_help": "Cuando se creen nuevas copias de respaldo, comprimir los archivos (.tar.gz) en lugar de descomprimir los archivos (.tar). N.B.: activar esta opción quiere decir que los archivos serán más pequeños pero que el proceso tardará más y utilizará más CPU.",
"global_settings_setting_security_experimental_enabled_help": "Habilite las funciones de seguridad experimentales (¡no habilite esto si no sabe lo que está haciendo!)",
"global_settings_setting_nginx_compatibility_help": "Compromiso entre compatibilidad y seguridad para el servidor web NGINX. Afecta al cifrado (y otros aspectos relacionados con la seguridad)",
"global_settings_setting_nginx_redirect_to_https_help": "Redirija las solicitudes HTTP a HTTPs de forma predeterminada (¡NO LO DESACTIVE a menos que realmente sepa lo que está haciendo!)",
"global_settings_setting_admin_strength": "Seguridad de la contraseña del administrador",
"global_settings_setting_user_strength": "Seguridad de la contraseña de usuario",
"global_settings_setting_postfix_compatibility_help": "Compromiso entre compatibilidad y seguridad para el servidor Postfix. Afecta al cifrado (y otros aspectos relacionados con la seguridad)",
"global_settings_setting_ssh_compatibility_help": "Compromiso entre compatibilidad y seguridad para el servidor SSH. Afecta al cifrado (y otros aspectos relacionados con la seguridad)",
"global_settings_setting_ssh_password_authentication_help": "Permitir autenticación de contraseña para SSH",
"global_settings_setting_ssh_port": "Puerto SSH",
"global_settings_setting_webadmin_allowlist_help": "Direcciones IP permitidas para acceder al webadmin. Separado por comas.",
"global_settings_setting_webadmin_allowlist_enabled_help": "Permita que solo algunas IP accedan al administrador web.",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "Permitir el uso de la llave (obsoleta) DSA para la configuración del demonio SSH",
"global_settings_setting_smtp_allow_ipv6_help": "Permitir el uso de IPv6 para enviar y recibir correo",
"global_settings_setting_smtp_relay_enabled_help": "El servidor relay de SMTP para enviar correo en lugar de esta instalación YunoHost. Útil si estás en una de estas situaciones: tu puerto 25 esta bloqueado por tu ISP o VPS, si estás en usado una IP marcada como residencial o DUHL, si no puedes configurar un DNS inverso o si el servidor no está directamente expuesto a internet y quieres utilizar otro servidor para enviar correos."
} }

View file

@ -254,7 +254,6 @@
"firewall_reloaded": "Suebakia birkargatu da", "firewall_reloaded": "Suebakia birkargatu da",
"domain_unknown": "'{domain}' domeinua ezezaguna da", "domain_unknown": "'{domain}' domeinua ezezaguna da",
"global_settings_cant_serialize_settings": "Ezinezkoa izan da konfikurazio-datuak serializatzea, zergatia: {reason}", "global_settings_cant_serialize_settings": "Ezinezkoa izan da konfikurazio-datuak serializatzea, zergatia: {reason}",
"global_settings_setting_security_nginx_redirect_to_https": "Birbideratu HTTP eskaerak HTTPSra (EZ ITZALI hau ez badakizu zertan ari zaren!)",
"group_deleted": "'{group}' taldea ezabatu da", "group_deleted": "'{group}' taldea ezabatu da",
"invalid_password": "Pasahitza ez da zuzena", "invalid_password": "Pasahitza ez da zuzena",
"log_domain_main_domain": "Lehenetsi '{}' domeinua", "log_domain_main_domain": "Lehenetsi '{}' domeinua",
@ -284,18 +283,13 @@
"global_settings_cant_write_settings": "Ezinezkoa izan da konfigurazio fitxategia gordetzea, zergatia: {reason}", "global_settings_cant_write_settings": "Ezinezkoa izan da konfigurazio fitxategia gordetzea, zergatia: {reason}",
"dyndns_domain_not_provided": "{provider} DynDNS enpresak ezin du {domain} domeinua eskaini.", "dyndns_domain_not_provided": "{provider} DynDNS enpresak ezin du {domain} domeinua eskaini.",
"firewall_reload_failed": "Ezinezkoa izan da suebakia birkargatzea", "firewall_reload_failed": "Ezinezkoa izan da suebakia birkargatzea",
"global_settings_setting_security_password_admin_strength": "Administrazio-pasahitzaren segurtasuna",
"hook_name_unknown": "'{name}' 'hook' izen ezezaguna", "hook_name_unknown": "'{name}' 'hook' izen ezezaguna",
"domain_deletion_failed": "Ezinezkoa izan da {domain} ezabatzea: {error}", "domain_deletion_failed": "Ezinezkoa izan da {domain} ezabatzea: {error}",
"global_settings_setting_security_nginx_compatibility": "Bateragarritasun eta segurtasun arteko gatazka NGINX web zerbitzarirako. Zifraketari eragiten dio (eta segurtasunari lotutako beste kontu batzuei)",
"log_regen_conf": "Berregin '{}' sistemaren konfigurazioa", "log_regen_conf": "Berregin '{}' sistemaren konfigurazioa",
"dpkg_lock_not_available": "Ezin da komando hau une honetan exekutatu beste aplikazio batek dpkg (sistemaren paketeen kudeatzailea) blokeatuta duelako, erabiltzen ari baita", "dpkg_lock_not_available": "Ezin da komando hau une honetan exekutatu beste aplikazio batek dpkg (sistemaren paketeen kudeatzailea) blokeatuta duelako, erabiltzen ari baita",
"group_created": "'{group}' taldea sortu da", "group_created": "'{group}' taldea sortu da",
"global_settings_setting_security_password_user_strength": "Erabiltzaile-pasahitzaren segurtasuna",
"global_settings_setting_security_experimental_enabled": "Gaitu segurtasun funtzio esperimentalak (ez ezazu egin ez badakizu zertan ari zaren!)",
"good_practices_about_admin_password": "Administrazio-pasahitz berria ezartzear zaude. Pasahitzak 8 karaktere izan beharko lituzke gutxienez, baina gomendagarria da pasahitz luzeagoa erabiltzea (esaldi bat, esaterako) edota karaktere desberdinak erabiltzea (hizki larriak, txikiak, zenbakiak eta karaktere bereziak).", "good_practices_about_admin_password": "Administrazio-pasahitz berria ezartzear zaude. Pasahitzak 8 karaktere izan beharko lituzke gutxienez, baina gomendagarria da pasahitz luzeagoa erabiltzea (esaldi bat, esaterako) edota karaktere desberdinak erabiltzea (hizki larriak, txikiak, zenbakiak eta karaktere bereziak).",
"log_help_to_get_failed_log": "Ezin izan da '{desc}' eragiketa exekutatu. Mesedez, laguntza nahi baduzu partekatu eragiketa honen erregistro osoa 'yunohost log share {name}' komandoa erabiliz", "log_help_to_get_failed_log": "Ezin izan da '{desc}' eragiketa exekutatu. Mesedez, laguntza nahi baduzu partekatu eragiketa honen erregistro osoa 'yunohost log share {name}' komandoa erabiliz",
"global_settings_setting_security_webadmin_allowlist_enabled": "Baimendu IP zehatz batzuk bakarrik administrazio-atarian.",
"group_unknown": "'{group}' taldea ezezaguna da", "group_unknown": "'{group}' taldea ezezaguna da",
"group_updated": "'{group}' taldea eguneratu da", "group_updated": "'{group}' taldea eguneratu da",
"group_update_failed": "Ezinezkoa izan da '{group}' taldea eguneratzea: {error}", "group_update_failed": "Ezinezkoa izan da '{group}' taldea eguneratzea: {error}",
@ -317,7 +311,6 @@
"domain_dns_push_success": "DNS ezarpenak eguneratu dira!", "domain_dns_push_success": "DNS ezarpenak eguneratu dira!",
"domain_dns_push_failed": "DNS ezarpenen eguneratzeak kale egin du.", "domain_dns_push_failed": "DNS ezarpenen eguneratzeak kale egin du.",
"domain_dns_push_partial_failure": "DNS ezarpenak erdipurdi eguneratu dira: jakinarazpen/errore batzuk egon dira.", "domain_dns_push_partial_failure": "DNS ezarpenak erdipurdi eguneratu dira: jakinarazpen/errore batzuk egon dira.",
"global_settings_setting_smtp_relay_host": "YunoHosten ordez posta elektronikoa bidaltzeko SMTP relay helbidea. Erabilgarri izan daiteke egoera hauetan: operadore edo VPS enpresak 25. ataka blokeatzen badu, DUHLen zure etxeko IPa ageri bada, ezin baduzu alderantzizko DNSa ezarri edo zerbitzari hau ez badago zuzenean internetera konektatuta baina posta elektronikoa bidali nahi baduzu.",
"group_deletion_failed": "Ezinezkoa izan da '{group}' taldea ezabatzea: {error}", "group_deletion_failed": "Ezinezkoa izan da '{group}' taldea ezabatzea: {error}",
"invalid_number_min": "{min} baino handiagoa izan behar da", "invalid_number_min": "{min} baino handiagoa izan behar da",
"invalid_number_max": "{max} baino txikiagoa izan behar da", "invalid_number_max": "{max} baino txikiagoa izan behar da",
@ -345,7 +338,6 @@
"domain_config_auth_application_key": "Aplikazioaren gakoa", "domain_config_auth_application_key": "Aplikazioaren gakoa",
"domain_config_auth_application_secret": "Aplikazioaren gako sekretua", "domain_config_auth_application_secret": "Aplikazioaren gako sekretua",
"domain_config_auth_consumer_key": "Erabiltzailearen gakoa", "domain_config_auth_consumer_key": "Erabiltzailearen gakoa",
"global_settings_setting_smtp_allow_ipv6": "Baimendu IPv6 posta elektronikoa jaso eta bidaltzeko",
"group_cannot_be_deleted": "{group} taldea ezin da eskuz ezabatu.", "group_cannot_be_deleted": "{group} taldea ezin da eskuz ezabatu.",
"log_domain_config_set": "Aldatu '{}' domeinuko ezarpenak", "log_domain_config_set": "Aldatu '{}' domeinuko ezarpenak",
"log_domain_dns_push": "Bidali '{}' domeinuaren DNS ezarpenak", "log_domain_dns_push": "Bidali '{}' domeinuaren DNS ezarpenak",
@ -359,8 +351,6 @@
"domain_config_mail_out": "Bidalitako mezuak", "domain_config_mail_out": "Bidalitako mezuak",
"domain_config_xmpp": "Bat-bateko mezularitza (XMPP)", "domain_config_xmpp": "Bat-bateko mezularitza (XMPP)",
"global_settings_bad_choice_for_enum": "{setting} ezarpenerako aukera okerra. '{choice}' ezarri da baina hauek dira aukerak: {available_choices}", "global_settings_bad_choice_for_enum": "{setting} ezarpenerako aukera okerra. '{choice}' ezarri da baina hauek dira aukerak: {available_choices}",
"global_settings_setting_security_postfix_compatibility": "Bateragarritasun eta segurtasun arteko gatazka Postfix zerbitzarirako. Zifraketari eragiten dio (eta segurtasunari lotutako beste kontu batzuei)",
"global_settings_setting_security_ssh_compatibility": "Bateragarritasun eta segurtasun arteko gatazka SSH zerbitzarirako. Zifraketari eragiten dio (eta segurtasunari lotutako beste kontu batzuei)",
"good_practices_about_user_password": "Erabiltzaile-pasahitz berria ezartzear zaude. Pasahitzak 8 karaktere izan beharko lituzke gutxienez, baina gomendagarria da pasahitz luzeagoa erabiltzea (esaldi bat, esaterako) edota karaktere desberdinak erabiltzea (hizki larriak, txikiak, zenbakiak eta karaktere bereziak).", "good_practices_about_user_password": "Erabiltzaile-pasahitz berria ezartzear zaude. Pasahitzak 8 karaktere izan beharko lituzke gutxienez, baina gomendagarria da pasahitz luzeagoa erabiltzea (esaldi bat, esaterako) edota karaktere desberdinak erabiltzea (hizki larriak, txikiak, zenbakiak eta karaktere bereziak).",
"group_cannot_edit_all_users": "'all_users' taldea ezin da eskuz moldatu. YunoHosten izena emanda dauden erabiltzaile guztiak barne dituen talde berezia da", "group_cannot_edit_all_users": "'all_users' taldea ezin da eskuz moldatu. YunoHosten izena emanda dauden erabiltzaile guztiak barne dituen talde berezia da",
"invalid_number": "Zenbaki bat izan behar da", "invalid_number": "Zenbaki bat izan behar da",
@ -416,8 +406,6 @@
"diagnosis_ports_forwarding_tip": "Arazoa konpontzeko, litekeena da operadorearen routerrean ataken birbideraketa konfiguratu behar izatea, <a href='https://yunohost.org/isp_box_config'>https://yunohost.org/isp_box_config</a>-n agertzen den bezala", "diagnosis_ports_forwarding_tip": "Arazoa konpontzeko, litekeena da operadorearen routerrean ataken birbideraketa konfiguratu behar izatea, <a href='https://yunohost.org/isp_box_config'>https://yunohost.org/isp_box_config</a>-n agertzen den bezala",
"domain_creation_failed": "Ezinezkoa izan da {domain} domeinua sortzea: {error}", "domain_creation_failed": "Ezinezkoa izan da {domain} domeinua sortzea: {error}",
"domains_available": "Erabilgarri dauden domeinuak:", "domains_available": "Erabilgarri dauden domeinuak:",
"global_settings_setting_pop3_enabled": "Gaitu POP3 protokoloa posta zerbitzarirako",
"global_settings_setting_security_ssh_port": "SSH ataka",
"global_settings_unknown_type": "Gertaera ezezaguna, {setting} ezarpenak {unknown_type} mota duela dirudi baina mota hori ez da sistemarekin bateragarria.", "global_settings_unknown_type": "Gertaera ezezaguna, {setting} ezarpenak {unknown_type} mota duela dirudi baina mota hori ez da sistemarekin bateragarria.",
"group_already_exist_on_system": "{group} taldea existitzen da dagoeneko sistemaren taldeetan", "group_already_exist_on_system": "{group} taldea existitzen da dagoeneko sistemaren taldeetan",
"diagnosis_processes_killed_by_oom_reaper": "Memoria agortu eta sistemak prozesu batzuk amaituarazi behar izan ditu. Honek esan nahi du sistemak ez duela memoria nahikoa edo prozesuren batek memoria gehiegi behar duela. Amaituarazi d(ir)en prozesua(k):\n{kills_summary}", "diagnosis_processes_killed_by_oom_reaper": "Memoria agortu eta sistemak prozesu batzuk amaituarazi behar izan ditu. Honek esan nahi du sistemak ez duela memoria nahikoa edo prozesuren batek memoria gehiegi behar duela. Amaituarazi d(ir)en prozesua(k):\n{kills_summary}",
@ -440,7 +428,6 @@
"global_settings_reset_success": "Lehengo ezarpenak {path}-n gorde dira", "global_settings_reset_success": "Lehengo ezarpenak {path}-n gorde dira",
"global_settings_unknown_setting_from_settings_file": "Gako ezezaguna ezarpenetan: '{setting_key}', baztertu eta gorde ezazu hemen: /etc/yunohost/settings-unknown.json", "global_settings_unknown_setting_from_settings_file": "Gako ezezaguna ezarpenetan: '{setting_key}', baztertu eta gorde ezazu hemen: /etc/yunohost/settings-unknown.json",
"domain_remove_confirm_apps_removal": "Domeinu hau ezabatzean aplikazio hauek desinstalatuko dira:\n{apps}\n\nZiur al zaude? [{answers}]", "domain_remove_confirm_apps_removal": "Domeinu hau ezabatzean aplikazio hauek desinstalatuko dira:\n{apps}\n\nZiur al zaude? [{answers}]",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Baimendu DSA gakoa (zaharkitua) SSH zerbitzuaren konfiguraziorako",
"hook_list_by_invalid": "Aukera hau ezin da 'hook'ak zerrendatzeko erabili", "hook_list_by_invalid": "Aukera hau ezin da 'hook'ak zerrendatzeko erabili",
"installation_complete": "Instalazioa amaitu da", "installation_complete": "Instalazioa amaitu da",
"hook_exec_failed": "Ezinezkoa izan da agindua exekutatzea: {path}", "hook_exec_failed": "Ezinezkoa izan da agindua exekutatzea: {path}",
@ -468,8 +455,6 @@
"global_settings_bad_type_for_setting": "{setting} ezarpenerako mota okerra. {received_type} ezarri da, {expected_type} espero zen", "global_settings_bad_type_for_setting": "{setting} ezarpenerako mota okerra. {received_type} ezarri da, {expected_type} espero zen",
"diagnosis_mail_fcrdns_dns_missing": "Ez da alderantzizko DNSrik ezarri IPv{ipversion}rako. Litekeena da hartzaileak posta elektroniko batzuk jaso ezin izatea edo mezuok spam modura etiketatuak izatea.", "diagnosis_mail_fcrdns_dns_missing": "Ez da alderantzizko DNSrik ezarri IPv{ipversion}rako. Litekeena da hartzaileak posta elektroniko batzuk jaso ezin izatea edo mezuok spam modura etiketatuak izatea.",
"log_backup_create": "Sortu babeskopia fitxategia", "log_backup_create": "Sortu babeskopia fitxategia",
"global_settings_setting_backup_compress_tar_archives": "Babeskopia berriak sortzean, konprimitu fitxategiak (.tar.gz) konprimitu gabeko fitxategien (.tar) ordez. Aukera hau gaitzean babeskopiek espazio gutxiago beharko dute, baina hasierako prozesua luzeagoa izango da eta CPUari lan handiagoa eragingo dio.",
"global_settings_setting_security_webadmin_allowlist": "Administrazio-ataria bisita dezaketen IP helbideak, koma bidez bereiziak.",
"global_settings_key_doesnt_exists": "'{settings_key}' gakoa ez da existitzen konfigurazio orokorrean; erabilgarri dauden gakoak ikus ditzakezu 'yunohost settings list' exekutatuz", "global_settings_key_doesnt_exists": "'{settings_key}' gakoa ez da existitzen konfigurazio orokorrean; erabilgarri dauden gakoak ikus ditzakezu 'yunohost settings list' exekutatuz",
"global_settings_setting_ssowat_panel_overlay_enabled": "Gaitu SSOwat paneleko \"overlay\"a", "global_settings_setting_ssowat_panel_overlay_enabled": "Gaitu SSOwat paneleko \"overlay\"a",
"log_backup_restore_system": "Lehengoratu sistema babeskopia fitxategi batetik", "log_backup_restore_system": "Lehengoratu sistema babeskopia fitxategi batetik",
@ -677,13 +662,27 @@
"migration_0021_cleaning_up": "Cachea eta erabilgarriak ez diren paketeak garbitzen…", "migration_0021_cleaning_up": "Cachea eta erabilgarriak ez diren paketeak garbitzen…",
"migration_0021_patch_yunohost_conflicts": "Arazo gatazkatsu bati adabakia jartzen…", "migration_0021_patch_yunohost_conflicts": "Arazo gatazkatsu bati adabakia jartzen…",
"migration_description_0021_migrate_to_bullseye": "Eguneratu sistema Debian Bullseye eta Yunohost 11.x-ra", "migration_description_0021_migrate_to_bullseye": "Eguneratu sistema Debian Bullseye eta Yunohost 11.x-ra",
"global_settings_setting_security_ssh_password_authentication": "Baimendu pasahitz bidezko autentikazioa SSHrako",
"migration_0021_problematic_apps_warning": "Mesedez, kontuan izan ziur asko gatazkatsuak izango diren odorengo aplikazioak aurkitu direla. Badirudi ez zirela YunoHost aplikazioen katalogotik instalatu, edo ez daude 'badabiltza' bezala etiketatuak. Ondorioz, ezin da bermatu eguneratu ondoren funtzionatzen jarraituko dutenik: {problematic_apps}", "migration_0021_problematic_apps_warning": "Mesedez, kontuan izan ziur asko gatazkatsuak izango diren odorengo aplikazioak aurkitu direla. Badirudi ez zirela YunoHost aplikazioen katalogotik instalatu, edo ez daude 'badabiltza' bezala etiketatuak. Ondorioz, ezin da bermatu eguneratu ondoren funtzionatzen jarraituko dutenik: {problematic_apps}",
"migration_0023_not_enough_space": "{path}-en ez dago toki nahikorik migrazioa abiarazteko.", "migration_0023_not_enough_space": "{path}-en ez dago toki nahikorik migrazioa abiarazteko.",
"migration_0023_postgresql_11_not_installed": "PostgreSQL ez zegoen zure isteman instalatuta. Ez dago egitekorik.", "migration_0023_postgresql_11_not_installed": "PostgreSQL ez zegoen zure isteman instalatuta. Ez dago egitekorik.",
"migration_0023_postgresql_13_not_installed": "PostgreSQL 11 dago instalatuta baina PostgreSQL 13 ez!? Zerbait arraroa gertatu omen zaio zure sistemari :( …", "migration_0023_postgresql_13_not_installed": "PostgreSQL 11 dago instalatuta baina PostgreSQL 13 ez!? Zerbait arraroa gertatu omen zaio zure sistemari :( …",
"migration_description_0022_php73_to_php74_pools": "Migratu php7.3-fpm 'pool' ezarpen-fitxategiak php7.4ra", "migration_description_0022_php73_to_php74_pools": "Migratu php7.3-fpm 'pool' ezarpen-fitxategiak php7.4ra",
"migration_description_0023_postgresql_11_to_13": "Migratu datubaseak PostgreSQL 11tik 13ra", "migration_description_0023_postgresql_11_to_13": "Migratu datubaseak PostgreSQL 11tik 13ra",
"global_settings_setting_backup_compress_tar_archives_help": "Babeskopia berriak sortzean, konprimitu fitxategiak (.tar.gz) konprimitu gabeko fitxategien (.tar) ordez. Aukera hau gaitzean babeskopiek espazio gutxiago beharko dute, baina hasierako prozesua luzeagoa izango da eta CPUari lan handiagoa eragingo dio.",
"global_settings_setting_security_experimental_enabled_help": "Gaitu segurtasun funtzio esperimentalak (ez ezazu egin ez badakizu zertan ari zaren!)",
"global_settings_setting_nginx_compatibility_help": "Bateragarritasun eta segurtasun arteko gatazka NGINX web zerbitzarirako. Zifraketari eragiten dio (eta segurtasunari lotutako beste kontu batzuei)",
"global_settings_setting_nginx_redirect_to_https_help": "Birbideratu HTTP eskaerak HTTPSra (EZ ITZALI hau ez badakizu zertan ari zaren!)",
"global_settings_setting_admin_strength": "Administrazio-pasahitzaren segurtasuna",
"global_settings_setting_user_strength": "Erabiltzaile-pasahitzaren segurtasuna",
"global_settings_setting_postfix_compatibility_help": "Bateragarritasun eta segurtasun arteko gatazka Postfix zerbitzarirako. Zifraketari eragiten dio (eta segurtasunari lotutako beste kontu batzuei)",
"global_settings_setting_ssh_compatibility_help": "Bateragarritasun eta segurtasun arteko gatazka SSH zerbitzarirako. Zifraketari eragiten dio (eta segurtasunari lotutako beste kontu batzuei)",
"global_settings_setting_ssh_password_authentication_help": "Baimendu pasahitz bidezko autentikazioa SSHrako",
"global_settings_setting_ssh_port": "SSH ataka",
"global_settings_setting_webadmin_allowlist_help": "Administrazio-ataria bisita dezaketen IP helbideak, koma bidez bereiziak.",
"global_settings_setting_webadmin_allowlist_enabled_help": "Baimendu IP zehatz batzuk bakarrik administrazio-atarian.",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "Baimendu DSA gakoa (zaharkitua) SSH zerbitzuaren konfiguraziorako",
"global_settings_setting_smtp_allow_ipv6_help": "Baimendu IPv6 posta elektronikoa jaso eta bidaltzeko",
"global_settings_setting_smtp_relay_enabled_help": "YunoHosten ordez posta elektronikoa bidaltzeko SMTP relay helbidea. Erabilgarri izan daiteke egoera hauetan: operadore edo VPS enpresak 25. ataka blokeatzen badu, DUHLen zure etxeko IPa ageri bada, ezin baduzu alderantzizko DNSa ezarri edo zerbitzari hau ez badago zuzenean internetera konektatuta baina posta elektronikoa bidali nahi baduzu.",
"migration_0024_rebuild_python_venv_broken_app": "{app} aplikazioari ez ikusiarena egin zaio ezin delako ingurune birtuala modu errazean birsortu. Horren ordez, aplikazioaren eguneraketa behartzen saia zaitezke `yunohost app upgrade --force {app}` arazoa konpontzeko.", "migration_0024_rebuild_python_venv_broken_app": "{app} aplikazioari ez ikusiarena egin zaio ezin delako ingurune birtuala modu errazean birsortu. Horren ordez, aplikazioaren eguneraketa behartzen saia zaitezke `yunohost app upgrade --force {app}` arazoa konpontzeko.",
"migration_0024_rebuild_python_venv_disclaimer_rebuild": "Ondorengo aplikazioen virtualenv-a birsortzeko saiakera egingo da (eragiketak luze jo dezake!): {rebuild_apps}", "migration_0024_rebuild_python_venv_disclaimer_rebuild": "Ondorengo aplikazioen virtualenv-a birsortzeko saiakera egingo da (eragiketak luze jo dezake!): {rebuild_apps}",
"migration_0024_rebuild_python_venv_disclaimer_ignored": "Virtualenv-ak ezin dira birsortu aplikazio horientzat. Eguneraketa behartu behar duzu horientzat, ondorengo komandoa exekutatuz egin daiteke: `yunohost app upgrade --force APP`: {ignored_apps}", "migration_0024_rebuild_python_venv_disclaimer_ignored": "Virtualenv-ak ezin dira birsortu aplikazio horientzat. Eguneraketa behartu behar duzu horientzat, ondorengo komandoa exekutatuz egin daiteke: `yunohost app upgrade --force APP`: {ignored_apps}",

View file

@ -302,25 +302,11 @@
"good_practices_about_user_password": "گذرواژه باید حداقل 8 کاراکتر باشد - اگرچه استفاده از گذرواژه طولانی تر تمرین خوبی است (به عنوان مثال عبارت عبور) و/یا استفاده از تنوع کاراکترها (بزرگ ، کوچک ، رقم و کاراکتر های خاص).", "good_practices_about_user_password": "گذرواژه باید حداقل 8 کاراکتر باشد - اگرچه استفاده از گذرواژه طولانی تر تمرین خوبی است (به عنوان مثال عبارت عبور) و/یا استفاده از تنوع کاراکترها (بزرگ ، کوچک ، رقم و کاراکتر های خاص).",
"good_practices_about_admin_password": "اکنون می خواهید گذرواژه جدیدی برای مدیریت تعریف کنید. گذرواژه باید حداقل 8 کاراکتر باشد - اگرچه استفاده از گذرواژه طولانی تر تمرین خوبی است (به عنوان مثال عبارت عبور) و/یا استفاده از تنوع کاراکترها (بزرگ ، کوچک ، رقم و کاراکتر های خاص).", "good_practices_about_admin_password": "اکنون می خواهید گذرواژه جدیدی برای مدیریت تعریف کنید. گذرواژه باید حداقل 8 کاراکتر باشد - اگرچه استفاده از گذرواژه طولانی تر تمرین خوبی است (به عنوان مثال عبارت عبور) و/یا استفاده از تنوع کاراکترها (بزرگ ، کوچک ، رقم و کاراکتر های خاص).",
"global_settings_unknown_type": "وضعیت غیرمنتظره ، به نظر می رسد که تنظیمات {setting} دارای نوع {unknown_type} است اما از نوع پشتیبانی شده توسط سیستم نیست.", "global_settings_unknown_type": "وضعیت غیرمنتظره ، به نظر می رسد که تنظیمات {setting} دارای نوع {unknown_type} است اما از نوع پشتیبانی شده توسط سیستم نیست.",
"global_settings_setting_backup_compress_tar_archives": "هنگام ایجاد پشتیبان جدید ، بایگانی های فشرده (.tar.gz) را به جای بایگانی های فشرده نشده (.tar) انتخاب کنید. N.B. : فعال کردن این گزینه به معنای ایجاد آرشیوهای پشتیبان سبک تر است ، اما روش پشتیبان گیری اولیه به طور قابل توجهی طولانی تر و سنگین تر بر روی CPU خواهد بود.",
"global_settings_setting_security_experimental_enabled": "فعال کردن ویژگی های امنیتی آزمایشی (اگر نمی دانید در حال انجام چه کاری هستید این کار را انجام ندهید!)",
"global_settings_setting_security_webadmin_allowlist": "آدرس های IP که مجاز به دسترسی مدیر وب هستند. جدا شده با ویرگول.",
"global_settings_setting_security_webadmin_allowlist_enabled": "فقط به برخی از IP ها اجازه دسترسی به مدیریت وب را بدهید.",
"global_settings_setting_smtp_relay_password": "رمز عبور میزبان رله SMTP", "global_settings_setting_smtp_relay_password": "رمز عبور میزبان رله SMTP",
"global_settings_setting_smtp_relay_user": "حساب کاربری رله SMTP", "global_settings_setting_smtp_relay_user": "حساب کاربری رله SMTP",
"global_settings_setting_smtp_relay_port": "پورت رله SMTP", "global_settings_setting_smtp_relay_port": "پورت رله SMTP",
"global_settings_setting_smtp_relay_host": "میزبان رله SMTP برای ارسال نامه به جای این نمونه yunohost استفاده می شود. اگر در یکی از این شرایط قرار دارید مفید است: پورت 25 شما توسط ارائه دهنده ISP یا VPS شما مسدود شده است، شما یک IP مسکونی دارید که در DUHL ذکر شده است، نمی توانید DNS معکوس را پیکربندی کنید یا این سرور مستقیماً در اینترنت نمایش داده نمی شود و می خواهید از یکی دیگر برای ارسال ایمیل استفاده کنید.",
"global_settings_setting_smtp_allow_ipv6": "اجازه دهید از IPv6 برای دریافت و ارسال نامه استفاده شود",
"global_settings_setting_ssowat_panel_overlay_enabled": "همپوشانی پانل SSOwat را فعال کنید", "global_settings_setting_ssowat_panel_overlay_enabled": "همپوشانی پانل SSOwat را فعال کنید",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "اجازه دهید از کلید میزبان DSA (منسوخ شده) برای پیکربندی SH daemon استفاده شود",
"global_settings_unknown_setting_from_settings_file": "کلید ناشناخته در تنظیمات: '{setting_key}'، آن را کنار گذاشته و در /etc/yunohost/settings-unknown.json ذخیره کنید", "global_settings_unknown_setting_from_settings_file": "کلید ناشناخته در تنظیمات: '{setting_key}'، آن را کنار گذاشته و در /etc/yunohost/settings-unknown.json ذخیره کنید",
"global_settings_setting_security_ssh_port": "درگاه SSH",
"global_settings_setting_security_postfix_compatibility": "سازگاری در مقابل مبادله امنیتی برای سرور Postfix. روی رمزها (و سایر جنبه های مرتبط با امنیت) تأثیر می گذارد",
"global_settings_setting_security_ssh_compatibility": "سازگاری در مقابل مبادله امنیتی برای سرور SSH. روی رمزها (و سایر جنبه های مرتبط با امنیت) تأثیر می گذارد",
"global_settings_setting_security_password_user_strength": "قدرت رمز عبور کاربر",
"global_settings_setting_security_password_admin_strength": "قدرت رمز عبور مدیر",
"global_settings_setting_security_nginx_compatibility": "سازگاری در مقابل مبادله امنیتی برای وب سرور NGINX. روی رمزها (و سایر جنبه های مرتبط با امنیت) تأثیر می گذارد",
"global_settings_setting_pop3_enabled": "پروتکل POP3 را برای سرور ایمیل فعال کنید",
"global_settings_reset_success": "تنظیمات قبلی اکنون در {path} پشتیبان گیری شده است", "global_settings_reset_success": "تنظیمات قبلی اکنون در {path} پشتیبان گیری شده است",
"global_settings_key_doesnt_exists": "کلید '{settings_key}' در تنظیمات جهانی وجود ندارد ، با اجرای 'لیست تنظیمات yunohost' می توانید همه کلیدهای موجود را مشاهده کنید", "global_settings_key_doesnt_exists": "کلید '{settings_key}' در تنظیمات جهانی وجود ندارد ، با اجرای 'لیست تنظیمات yunohost' می توانید همه کلیدهای موجود را مشاهده کنید",
"global_settings_cant_write_settings": "فایل تنظیمات ذخیره نشد، به دلیل: {reason}", "global_settings_cant_write_settings": "فایل تنظیمات ذخیره نشد، به دلیل: {reason}",
@ -589,5 +575,18 @@
"permission_deletion_failed": "اجازه '{permission}' حذف نشد: {error}", "permission_deletion_failed": "اجازه '{permission}' حذف نشد: {error}",
"permission_deleted": "مجوز '{permission}' حذف شد", "permission_deleted": "مجوز '{permission}' حذف شد",
"permission_cant_add_to_all_users": "مجوز {permission} را نمی توان به همه کاربران اضافه کرد.", "permission_cant_add_to_all_users": "مجوز {permission} را نمی توان به همه کاربران اضافه کرد.",
"permission_currently_allowed_for_all_users": "این مجوز در حال حاضر به همه کاربران علاوه بر آن گروه های دیگر نیز اعطا شده. احتمالاً بخواهید مجوز 'all_users' را حذف کنید یا سایر گروه هایی را که در حال حاضر مجوز به آنها اعطا شده است را هم حذف کنید." "permission_currently_allowed_for_all_users": "این مجوز در حال حاضر به همه کاربران علاوه بر آن گروه های دیگر نیز اعطا شده. احتمالاً بخواهید مجوز 'all_users' را حذف کنید یا سایر گروه هایی را که در حال حاضر مجوز به آنها اعطا شده است را هم حذف کنید.",
"global_settings_setting_backup_compress_tar_archives_help": "هنگام ایجاد پشتیبان جدید ، بایگانی های فشرده (.tar.gz) را به جای بایگانی های فشرده نشده (.tar) انتخاب کنید. N.B. : فعال کردن این گزینه به معنای ایجاد آرشیوهای پشتیبان سبک تر است ، اما روش پشتیبان گیری اولیه به طور قابل توجهی طولانی تر و سنگین تر بر روی CPU خواهد بود.",
"global_settings_setting_security_experimental_enabled_help": "فعال کردن ویژگی های امنیتی آزمایشی (اگر نمی دانید در حال انجام چه کاری هستید این کار را انجام ندهید!)",
"global_settings_setting_nginx_compatibility_help": "سازگاری در مقابل مبادله امنیتی برای وب سرور NGINX. روی رمزها (و سایر جنبه های مرتبط با امنیت) تأثیر می گذارد",
"global_settings_setting_admin_strength": "قدرت رمز عبور مدیر",
"global_settings_setting_user_strength": "قدرت رمز عبور کاربر",
"global_settings_setting_postfix_compatibility_help": "سازگاری در مقابل مبادله امنیتی برای سرور Postfix. روی رمزها (و سایر جنبه های مرتبط با امنیت) تأثیر می گذارد",
"global_settings_setting_ssh_compatibility_help": "سازگاری در مقابل مبادله امنیتی برای سرور SSH. روی رمزها (و سایر جنبه های مرتبط با امنیت) تأثیر می گذارد",
"global_settings_setting_ssh_port": "درگاه SSH",
"global_settings_setting_webadmin_allowlist_help": "آدرس های IP که مجاز به دسترسی مدیر وب هستند. جدا شده با ویرگول.",
"global_settings_setting_webadmin_allowlist_enabled_help": "فقط به برخی از IP ها اجازه دسترسی به مدیریت وب را بدهید.",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "اجازه دهید از کلید میزبان DSA (منسوخ شده) برای پیکربندی SH daemon استفاده شود",
"global_settings_setting_smtp_allow_ipv6_help": "اجازه دهید از IPv6 برای دریافت و ارسال نامه استفاده شود",
"global_settings_setting_smtp_relay_enabled_help": "میزبان رله SMTP برای ارسال نامه به جای این نمونه yunohost استفاده می شود. اگر در یکی از این شرایط قرار دارید مفید است: پورت 25 شما توسط ارائه دهنده ISP یا VPS شما مسدود شده است، شما یک IP مسکونی دارید که در DUHL ذکر شده است، نمی توانید DNS معکوس را پیکربندی کنید یا این سرور مستقیماً در اینترنت نمایش داده نمی شود و می خواهید از یکی دیگر برای ارسال ایمیل استفاده کنید."
} }

View file

@ -300,9 +300,6 @@
"dpkg_is_broken": "Vous ne pouvez pas faire ça maintenant car dpkg/apt (le gestionnaire de paquets du système) semble avoir laissé des choses non configurées. Vous pouvez essayer de résoudre ce problème en vous connectant via SSH et en exécutant `sudo apt install --fix-broken` et/ou `sudo dpkg --configure -a'.", "dpkg_is_broken": "Vous ne pouvez pas faire ça maintenant car dpkg/apt (le gestionnaire de paquets du système) semble avoir laissé des choses non configurées. Vous pouvez essayer de résoudre ce problème en vous connectant via SSH et en exécutant `sudo apt install --fix-broken` et/ou `sudo dpkg --configure -a'.",
"dyndns_could_not_check_available": "Impossible de vérifier si {domain} est disponible chez {provider}.", "dyndns_could_not_check_available": "Impossible de vérifier si {domain} est disponible chez {provider}.",
"file_does_not_exist": "Le fichier dont le chemin est {path} n'existe pas.", "file_does_not_exist": "Le fichier dont le chemin est {path} n'existe pas.",
"global_settings_setting_security_password_admin_strength": "Qualité du mot de passe administrateur",
"global_settings_setting_security_password_user_strength": "Qualité du mot de passe de l'utilisateur",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Autoriser l'utilisation de la clé hôte DSA (obsolète) pour la configuration du service SSH",
"hook_json_return_error": "Échec de la lecture au retour du script {path}. Erreur : {msg}. Contenu brut : {raw_content}", "hook_json_return_error": "Échec de la lecture au retour du script {path}. Erreur : {msg}. Contenu brut : {raw_content}",
"pattern_password_app": "Désolé, les mots de passe ne peuvent pas contenir les caractères suivants : {forbidden_chars}", "pattern_password_app": "Désolé, les mots de passe ne peuvent pas contenir les caractères suivants : {forbidden_chars}",
"root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.", "root_password_replaced_by_admin_password": "Votre mot de passe root a été remplacé par votre mot de passe administrateur.",
@ -326,9 +323,6 @@
"regenconf_now_managed_by_yunohost": "Le fichier de configuration '{conf}' est maintenant géré par YunoHost (catégorie {category}).", "regenconf_now_managed_by_yunohost": "Le fichier de configuration '{conf}' est maintenant géré par YunoHost (catégorie {category}).",
"regenconf_up_to_date": "La configuration est déjà à jour pour la catégorie '{category}'", "regenconf_up_to_date": "La configuration est déjà à jour pour la catégorie '{category}'",
"already_up_to_date": "Il n'y a rien à faire. Tout est déjà à jour.", "already_up_to_date": "Il n'y a rien à faire. Tout est déjà à jour.",
"global_settings_setting_security_nginx_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur web Nginx. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)",
"global_settings_setting_security_ssh_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur SSH. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)",
"global_settings_setting_security_postfix_compatibility": "Compatibilité versus compromis sécuritaire pour le serveur Postfix. Affecte les cryptogrammes (et d'autres aspects liés à la sécurité)",
"regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par 'regen-conf' (catégorie {category}) mais a été conservé.", "regenconf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par 'regen-conf' (catégorie {category}) mais a été conservé.",
"regenconf_updated": "La configuration a été mise à jour pour '{category}'", "regenconf_updated": "La configuration a été mise à jour pour '{category}'",
"regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'", "regenconf_would_be_updated": "La configuration aurait dû être mise à jour pour la catégorie '{category}'",
@ -474,7 +468,7 @@
"diagnosis_services_bad_status_tip": "Vous pouvez essayer de <a href='#/services/{service}'>redémarrer le service</a>, et si cela ne fonctionne pas, consultez <a href='#/services/{service}'>les journaux de service dans le webadmin</a> (à partir de la ligne de commande, vous pouvez le faire avec <cmd>yunohost service restart {service}</cmd> et <cmd>yunohost service log {service}</cmd> ).", "diagnosis_services_bad_status_tip": "Vous pouvez essayer de <a href='#/services/{service}'>redémarrer le service</a>, et si cela ne fonctionne pas, consultez <a href='#/services/{service}'>les journaux de service dans le webadmin</a> (à partir de la ligne de commande, vous pouvez le faire avec <cmd>yunohost service restart {service}</cmd> et <cmd>yunohost service log {service}</cmd> ).",
"diagnosis_http_bad_status_code": "Le système de diagnostique n'a pas réussi à contacter votre serveur. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration Nginx est à jour et qu'un reverse-proxy n'interfère pas.", "diagnosis_http_bad_status_code": "Le système de diagnostique n'a pas réussi à contacter votre serveur. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration Nginx est à jour et qu'un reverse-proxy n'interfère pas.",
"diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur depuis l'extérieur. Il semble être inaccessible.<br>1. La cause la plus fréquente pour ce problème est que les ports 80 et 443 <a href='https://yunohost.org/isp_box_config'> ne sont pas correctement redirigés vers votre serveur</a>.<br>2. Vous devriez également vérifier que le le service nginx est en cours d'exécution<br>3. Pour les installations plus complexes, assurez-vous qu'aucun pare-feu ou reverse-proxy n'interfère.", "diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur depuis l'extérieur. Il semble être inaccessible.<br>1. La cause la plus fréquente pour ce problème est que les ports 80 et 443 <a href='https://yunohost.org/isp_box_config'> ne sont pas correctement redirigés vers votre serveur</a>.<br>2. Vous devriez également vérifier que le le service nginx est en cours d'exécution<br>3. Pour les installations plus complexes, assurez-vous qu'aucun pare-feu ou reverse-proxy n'interfère.",
"global_settings_setting_pop3_enabled": "Activer le protocole POP3 pour le serveur de messagerie", "global_settings_setting_pop3_enabled_help": "Activer le protocole POP3 pour le serveur de messagerie",
"log_app_action_run": "Lancer l'action de l'application '{}'", "log_app_action_run": "Lancer l'action de l'application '{}'",
"diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu'il n'y a pas encore eu de diagnostic. Vous devriez en lancer un depuis la webadmin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.", "diagnosis_never_ran_yet": "Il apparaît que le serveur a été installé récemment et qu'il n'y a pas encore eu de diagnostic. Vous devriez en lancer un depuis la webadmin ou en utilisant 'yunohost diagnosis run' depuis la ligne de commande.",
"diagnosis_description_web": "Web", "diagnosis_description_web": "Web",
@ -499,7 +493,6 @@
"diagnosis_mail_queue_ok": "{nb_pending} emails en attente dans les files d'attente de messagerie", "diagnosis_mail_queue_ok": "{nb_pending} emails en attente dans les files d'attente de messagerie",
"diagnosis_mail_queue_unavailable_details": "Erreur : {error}", "diagnosis_mail_queue_unavailable_details": "Erreur : {error}",
"diagnosis_mail_queue_too_big": "Trop d'emails en attente dans la file d'attente ({nb_pending} emails)", "diagnosis_mail_queue_too_big": "Trop d'emails en attente dans la file d'attente ({nb_pending} emails)",
"global_settings_setting_smtp_allow_ipv6": "Autoriser l'utilisation d'IPv6 pour recevoir et envoyer du courrier",
"diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter 'yunohost diagnosis show --issues --human-readable' à partir de la ligne de commande.", "diagnosis_display_tip": "Pour voir les problèmes détectés, vous pouvez accéder à la section Diagnostic du webadmin ou exécuter 'yunohost diagnosis show --issues --human-readable' à partir de la ligne de commande.",
"diagnosis_ip_global": "IP globale : <code>{global}</code>", "diagnosis_ip_global": "IP globale : <code>{global}</code>",
"diagnosis_ip_local": "IP locale : <code>{local}</code>", "diagnosis_ip_local": "IP locale : <code>{local}</code>",
@ -536,7 +529,7 @@
"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.", "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.",
"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>.", "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.", "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.",
"global_settings_setting_backup_compress_tar_archives": "Lors de la création de nouvelles sauvegardes, compresser automatiquement les archives (.tar.gz) au lieu des archives non compressées (.tar). N.B. : activer cette option permet de créer des archives plus légères, mais la procédure de sauvegarde initiale sera significativement plus longues et plus gourmandes en CPU.", "global_settings_setting_backup_compress_tar_archives": "Compresser les archives de backup",
"diagnosis_processes_killed_by_oom_reaper": "Certains processus ont été récemment arrêtés par le système car il manquait de mémoire. Ceci est typiquement symptomatique d'un manque de mémoire sur le système ou d'un processus consommant trop de mémoire. Liste des processus arrêtés :\n{kills_summary}", "diagnosis_processes_killed_by_oom_reaper": "Certains processus ont été récemment arrêtés par le système car il manquait de mémoire. Ceci est typiquement symptomatique d'un manque de mémoire sur le système ou d'un processus consommant trop de mémoire. Liste des processus arrêtés :\n{kills_summary}",
"ask_user_domain": "Domaine à utiliser pour l'adresse email de l'utilisateur et le compte XMPP", "ask_user_domain": "Domaine à utiliser pour l'adresse email de l'utilisateur et le compte XMPP",
"app_manifest_install_ask_is_public": "Cette application devrait-elle être visible par les visiteurs anonymes ?", "app_manifest_install_ask_is_public": "Cette application devrait-elle être visible par les visiteurs anonymes ?",
@ -544,13 +537,13 @@
"app_manifest_install_ask_password": "Choisissez un mot de passe administrateur pour cette application", "app_manifest_install_ask_password": "Choisissez un mot de passe administrateur pour cette application",
"app_manifest_install_ask_path": "Choisissez le chemin d'URL (après le domaine) où cette application doit être installée", "app_manifest_install_ask_path": "Choisissez le chemin d'URL (après le domaine) où cette application doit être installée",
"app_manifest_install_ask_domain": "Choisissez le domaine sur lequel vous souhaitez installer cette application", "app_manifest_install_ask_domain": "Choisissez le domaine sur lequel vous souhaitez installer cette application",
"global_settings_setting_smtp_relay_host": "Adresse du relais SMTP",
"global_settings_setting_smtp_relay_user": "Compte utilisateur du relais SMTP", "global_settings_setting_smtp_relay_user": "Compte utilisateur du relais SMTP",
"global_settings_setting_smtp_relay_port": "Port du relais SMTP", "global_settings_setting_smtp_relay_port": "Port du relais SMTP",
"global_settings_setting_smtp_relay_host": "Relais SMTP à utiliser pour envoyer les mails au lieu de cette instance YunoHost. Cela est utile si vous êtes dans l'une de ces situations : le port 25 est bloqué par votre FAI ou votre fournisseur VPS ; vous avez une IP résidentielle répertoriée sur DUHL ; vous ne pouvez pas configurer le DNS inversé ; ou le serveur n'est pas directement accessible depuis Internet et vous voulez en utiliser un autre pour envoyer des mails.",
"diagnosis_package_installed_from_sury_details": "Certains paquets ont été installés par inadvertance à partir d'un dépôt tiers appelé Sury. L'équipe YunoHost a amélioré la stratégie de gestion de ces paquets, mais on s'attend à ce que certaines configurations qui ont installé des applications PHP7.3 tout en étant toujours sur Stretch présentent des incohérences. Pour résoudre cette situation, vous devez essayer d'exécuter la commande suivante : <cmd>{cmd_to_fix}</cmd>", "diagnosis_package_installed_from_sury_details": "Certains paquets ont été installés par inadvertance à partir d'un dépôt tiers appelé Sury. L'équipe YunoHost a amélioré la stratégie de gestion de ces paquets, mais on s'attend à ce que certaines configurations qui ont installé des applications PHP7.3 tout en étant toujours sur Stretch présentent des incohérences. Pour résoudre cette situation, vous devez essayer d'exécuter la commande suivante : <cmd>{cmd_to_fix}</cmd>",
"app_argument_password_no_default": "Erreur lors de l'analyse syntaxique du mot de passe '{name}' : le mot de passe ne peut pas avoir de valeur par défaut pour des raisons de sécurité", "app_argument_password_no_default": "Erreur lors de l'analyse syntaxique du mot de passe '{name}' : le mot de passe ne peut pas avoir de valeur par défaut pour des raisons de sécurité",
"pattern_email_forward": "L'adresse électronique doit être valide, le symbole '+' étant accepté (par exemple : johndoe+yunohost@exemple.com)", "pattern_email_forward": "L'adresse électronique doit être valide, le symbole '+' étant accepté (par exemple : johndoe+yunohost@exemple.com)",
"global_settings_setting_smtp_relay_password": "Mot de passe du relais de l'hôte SMTP", "global_settings_setting_smtp_relay_password": "Mot de passe du relais SMTP",
"diagnosis_package_installed_from_sury": "Des paquets du système devraient être rétrogradé de version", "diagnosis_package_installed_from_sury": "Des paquets du système devraient être rétrogradé de version",
"additional_urls_already_added": "URL supplémentaire '{url}' déjà ajoutée pour la permission '{permission}'", "additional_urls_already_added": "URL supplémentaire '{url}' déjà ajoutée pour la permission '{permission}'",
"unknown_main_domain_path": "Domaine ou chemin inconnu pour '{app}'. Vous devez spécifier un domaine et un chemin pour pouvoir spécifier une URL pour l'autorisation.", "unknown_main_domain_path": "Domaine ou chemin inconnu pour '{app}'. Vous devez spécifier un domaine et un chemin pour pouvoir spécifier une URL pour l'autorisation.",
@ -572,24 +565,20 @@
"app_restore_script_failed": "Une erreur s'est produite dans le script de restauration de l'application", "app_restore_script_failed": "Une erreur s'est produite dans le script de restauration de l'application",
"restore_backup_too_old": "Cette sauvegarde ne peut pas être restaurée car elle provient d'une version de YunoHost trop ancienne.", "restore_backup_too_old": "Cette sauvegarde ne peut pas être restaurée car elle provient d'une version de YunoHost trop ancienne.",
"log_backup_create": "Créer une archive de sauvegarde", "log_backup_create": "Créer une archive de sauvegarde",
"global_settings_setting_ssowat_panel_overlay_enabled": "Activer la superposition de la vignette SSOwat", "global_settings_setting_ssowat_panel_overlay_enabled": "Activer la vignette 'YunoHost' (raccourci vers le portail) sur les apps",
"migration_ldap_rollback_success": "Système rétabli dans son état initial.", "migration_ldap_rollback_success": "Système rétabli dans son état initial.",
"permission_cant_add_to_all_users": "L'autorisation {permission} ne peut pas être ajoutée à tous les utilisateurs.", "permission_cant_add_to_all_users": "L'autorisation {permission} ne peut pas être ajoutée à tous les utilisateurs.",
"migration_ldap_migration_failed_trying_to_rollback": "Impossible de migrer... tentative de restauration du système.", "migration_ldap_migration_failed_trying_to_rollback": "Impossible de migrer... tentative de restauration du système.",
"migration_ldap_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur : {error }", "migration_ldap_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur : {error }",
"migration_ldap_backup_before_migration": "Création d'une sauvegarde de la base de données LDAP et des paramètres des applications avant la migration proprement dite.", "migration_ldap_backup_before_migration": "Création d'une sauvegarde de la base de données LDAP et des paramètres des applications avant la migration proprement dite.",
"global_settings_setting_security_ssh_port": "Port SSH",
"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_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_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.", "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 webadmin. Elles doivent être séparées par une virgule.",
"global_settings_setting_security_webadmin_allowlist_enabled": "Autoriser seulement certaines IP à accéder à la webadmin.",
"diagnosis_dns_specialusedomain": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial comme .local ou .test et ne devrait donc pas avoir d'enregistrements DNS réels.", "diagnosis_dns_specialusedomain": "Le domaine {domain} est basé sur un domaine de premier niveau (TLD) à usage spécial comme .local ou .test et ne devrait donc pas avoir d'enregistrements DNS réels.",
"invalid_password": "Mot de passe incorrect", "invalid_password": "Mot de passe incorrect",
"ldap_server_is_down_restart_it": "Le service LDAP est en panne, essayez de le redémarrer...", "ldap_server_is_down_restart_it": "Le service LDAP est en panne, essayez de le redémarrer...",
"ldap_server_down": "Impossible d'atteindre le serveur LDAP", "ldap_server_down": "Impossible d'atteindre le serveur LDAP",
"global_settings_setting_security_experimental_enabled": "Activer les fonctionnalités de sécurité expérimentales (ne l'activez pas si vous ne savez pas ce que vous faites !)",
"diagnosis_apps_deprecated_practices": "La version installée de cette application utilise encore de très anciennes pratiques de packaging obsolètes et dépassées. Vous devriez vraiment envisager de mettre à jour cette application.", "diagnosis_apps_deprecated_practices": "La version installée de cette application utilise encore de très anciennes pratiques de packaging obsolètes et dépassées. Vous devriez vraiment envisager de mettre à jour cette application.",
"diagnosis_apps_outdated_ynh_requirement": "La version installée de cette application nécessite uniquement YunoHost >= 2.x ou 3.x, ce qui tend à indiquer qu'elle n'est pas à jour avec les pratiques recommandées de packaging et des helpers . Vous devriez vraiment envisager de la mettre à jour.", "diagnosis_apps_outdated_ynh_requirement": "La version installée de cette application nécessite uniquement YunoHost >= 2.x ou 3.x, ce qui tend à indiquer qu'elle n'est pas à jour avec les pratiques recommandées de packaging et des helpers . Vous devriez vraiment envisager de la mettre à jour.",
"diagnosis_apps_bad_quality": "Cette application est actuellement signalée comme cassée dans le catalogue d'applications de YunoHost. Cela peut être un problème temporaire. En attendant que les mainteneurs tentent de résoudre le problème, la mise à jour de cette application est désactivée.", "diagnosis_apps_bad_quality": "Cette application est actuellement signalée comme cassée dans le catalogue d'applications de YunoHost. Cela peut être un problème temporaire. En attendant que les mainteneurs tentent de résoudre le problème, la mise à jour de cette application est désactivée.",
@ -607,7 +596,6 @@
"user_import_bad_line": "Ligne incorrecte {line} : {details}", "user_import_bad_line": "Ligne incorrecte {line} : {details}",
"log_user_import": "Importer des utilisateurs", "log_user_import": "Importer des utilisateurs",
"diagnosis_high_number_auth_failures": "Il y a eu récemment un grand nombre d'échecs d'authentification. Assurez-vous que Fail2Ban est en cours d'exécution et est correctement configuré, ou utilisez un port personnalisé pour SSH comme expliqué dans https://yunohost.org/security.", "diagnosis_high_number_auth_failures": "Il y a eu récemment un grand nombre d'échecs d'authentification. Assurez-vous que Fail2Ban est en cours d'exécution et est correctement configuré, ou utilisez un port personnalisé pour SSH comme expliqué dans https://yunohost.org/security.",
"global_settings_setting_security_nginx_redirect_to_https": "Rediriger les requêtes HTTP vers HTTPS par défaut (NE PAS DÉSACTIVER à moins de savoir vraiment ce que vous faites !)",
"config_validate_color": "Doit être une couleur hexadécimale RVB valide", "config_validate_color": "Doit être une couleur hexadécimale RVB valide",
"app_config_unable_to_apply": "Échec de l'application des valeurs du panneau de configuration.", "app_config_unable_to_apply": "Échec de l'application des valeurs du panneau de configuration.",
"app_config_unable_to_read": "Échec de la lecture des valeurs du panneau de configuration.", "app_config_unable_to_read": "Échec de la lecture des valeurs du panneau de configuration.",
@ -675,7 +663,6 @@
"migration_0021_patch_yunohost_conflicts": "Application du correctif pour contourner le problème de conflit...", "migration_0021_patch_yunohost_conflicts": "Application du correctif pour contourner le problème de conflit...",
"migration_0021_not_buster2": "La distribution Debian actuelle n'est pas Buster ! Si vous avez déjà effectué la migration Buster->Bullseye, alors cette erreur est symptomatique du fait que la migration n'a pas été terminée correctement à 100% (sinon YunoHost aurait marqué la migration comme terminée). Il est recommandé d'étudier ce qu'il s'est passé avec l'équipe de support, qui aura besoin du log **complet** de la migration, qui peut être retrouvé dans Outils > Journaux dans la webadmin.", "migration_0021_not_buster2": "La distribution Debian actuelle n'est pas Buster ! Si vous avez déjà effectué la migration Buster->Bullseye, alors cette erreur est symptomatique du fait que la migration n'a pas été terminée correctement à 100% (sinon YunoHost aurait marqué la migration comme terminée). Il est recommandé d'étudier ce qu'il s'est passé avec l'équipe de support, qui aura besoin du log **complet** de la migration, qui peut être retrouvé dans Outils > Journaux dans la webadmin.",
"migration_description_0021_migrate_to_bullseye": "Mise à niveau du système vers Debian Bullseye et YunoHost 11.x", "migration_description_0021_migrate_to_bullseye": "Mise à niveau du système vers Debian Bullseye et YunoHost 11.x",
"global_settings_setting_security_ssh_password_authentication": "Autoriser l'authentification par mot de passe pour SSH",
"domain_config_default_app": "Application par défaut", "domain_config_default_app": "Application par défaut",
"migration_description_0022_php73_to_php74_pools": "Migration des fichiers de configuration php7.3-fpm 'pool' vers php7.4", "migration_description_0022_php73_to_php74_pools": "Migration des fichiers de configuration php7.3-fpm 'pool' vers php7.4",
"migration_description_0023_postgresql_11_to_13": "Migration des bases de données de PostgreSQL 11 vers 13", "migration_description_0023_postgresql_11_to_13": "Migration des bases de données de PostgreSQL 11 vers 13",
@ -685,6 +672,22 @@
"tools_upgrade_failed": "Impossible de mettre à jour les paquets : {packages_list}", "tools_upgrade_failed": "Impossible de mettre à jour les paquets : {packages_list}",
"migration_0023_not_enough_space": "Prévoyez suffisamment d'espace disponible dans {path} pour exécuter la migration.", "migration_0023_not_enough_space": "Prévoyez suffisamment d'espace disponible dans {path} pour exécuter la migration.",
"migration_0023_postgresql_11_not_installed": "PostgreSQL n'a pas été installé sur votre système. Il n'y a rien à faire.", "migration_0023_postgresql_11_not_installed": "PostgreSQL n'a pas été installé sur votre système. Il n'y a rien à faire.",
"global_settings_setting_backup_compress_tar_archives_help": "Lors de la création de nouvelles sauvegardes, compresser automatiquement les archives (.tar.gz) au lieu des archives non compressées (.tar). N.B. : activer cette option permet de créer des archives plus légères, mais la procédure de sauvegarde initiale sera significativement plus longues et plus gourmandes en CPU.",
"global_settings_setting_security_experimental_enabled": "Fonctionnalités de sécurité expérimentales",
"global_settings_setting_security_experimental_enabled_help": "Activer les fonctionnalités de sécurité expérimentales (ne l'activez pas si vous ne savez pas ce que vous faites !)",
"global_settings_setting_nginx_compatibility_help": "Compromis 'compatibilité versus sécurité' pour le serveur web Nginx. Affecte les cryptogrammes utilisés (et d'autres aspects liés à la sécurité)",
"global_settings_setting_nginx_redirect_to_https_help": "Rediriger les requêtes HTTP vers HTTPS par défaut (NE PAS DÉSACTIVER à moins de savoir vraiment ce que vous faites !)",
"global_settings_setting_admin_strength": "Critères pour les mots de passe administrateur",
"global_settings_setting_user_strength": "Critères pour les mots de passe utilisateurs",
"global_settings_setting_postfix_compatibility_help": "Compromis 'compatibilité versus sécurité' pour le serveur Postfix. Affecte les cryptogrammes utilisés (et d'autres aspects liés à la sécurité)",
"global_settings_setting_ssh_compatibility_help": "Compromis 'compatibilité versus sécurité' pour le serveur SSH. Affecte les cryptogrammes utilisés (et d'autres aspects liés à la sécurité)",
"global_settings_setting_ssh_password_authentication_help": "Autoriser l'authentification par mot de passe pour SSH",
"global_settings_setting_ssh_port": "Port SSH",
"global_settings_setting_webadmin_allowlist_help": "Adresses IP autorisées à accéder à la webadmin. Elles doivent être séparées par une virgule.",
"global_settings_setting_webadmin_allowlist_enabled_help": "Autoriser seulement certaines IP à accéder à la webadmin.",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "Autoriser l'utilisation de la clé hôte DSA (obsolète) pour la configuration du service SSH",
"global_settings_setting_smtp_allow_ipv6_help": "Autoriser l'utilisation d'IPv6 pour recevoir et envoyer du courrier",
"global_settings_setting_smtp_relay_enabled_help": "Un relais SMTP permet d'envoyer du courrier à la place de cette instance YunoHost. Cela est utile si vous êtes dans l'une de ces situations : le port 25 est bloqué par votre FAI ou par votre fournisseur VPS ; vous avez une IP résidentielle répertoriée sur DUHL ; vous ne pouvez pas configurer le DNS inversé ; ou le serveur n'est pas directement accessible depuis Internet et vous voulez en utiliser un autre pour envoyer des mails.",
"migration_0024_rebuild_python_venv_disclaimer_rebuild": "La reconstruction du virtualenv sera tentée pour les applications suivantes (NB : l'opération peut prendre un certain temps !) : {rebuild_apps}", "migration_0024_rebuild_python_venv_disclaimer_rebuild": "La reconstruction du virtualenv sera tentée pour les applications suivantes (NB : l'opération peut prendre un certain temps !) : {rebuild_apps}",
"migration_0024_rebuild_python_venv_in_progress": "Tentative de reconstruction du virtualenv Python pour `{app}`", "migration_0024_rebuild_python_venv_in_progress": "Tentative de reconstruction du virtualenv Python pour `{app}`",
"migration_0024_rebuild_python_venv_failed": "Échec de la reconstruction de l'environnement virtuel Python pour {app}. L'application peut ne pas fonctionner tant que ce problème n'est pas résolu. Vous devriez corriger la situation en forçant la mise à jour de cette application en utilisant `yunohost app upgrade --force {app}`.", "migration_0024_rebuild_python_venv_failed": "Échec de la reconstruction de l'environnement virtuel Python pour {app}. L'application peut ne pas fonctionner tant que ce problème n'est pas résolu. Vous devriez corriger la situation en forçant la mise à jour de cette application en utilisant `yunohost app upgrade --force {app}`.",

View file

@ -308,17 +308,8 @@
"domain_cannot_add_xmpp_upload": "Non podes engadir dominios que comecen con 'xmpp-upload.'. Este tipo de nome está reservado para a función se subida de XMPP integrada en YunoHost.", "domain_cannot_add_xmpp_upload": "Non podes engadir dominios que comecen con 'xmpp-upload.'. Este tipo de nome está reservado para a función se subida de XMPP integrada en YunoHost.",
"file_does_not_exist": "O ficheiro {path} non existe.", "file_does_not_exist": "O ficheiro {path} non existe.",
"firewall_reload_failed": "Non se puido recargar o cortalumes", "firewall_reload_failed": "Non se puido recargar o cortalumes",
"global_settings_setting_smtp_allow_ipv6": "Permitir o uso de IPv6 para recibir e enviar emais",
"global_settings_setting_ssowat_panel_overlay_enabled": "Activar as capas no panel SSOwat", "global_settings_setting_ssowat_panel_overlay_enabled": "Activar as capas no panel SSOwat",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Permitir o uso de DSA hostkey (en desuso) para a configuración do demoño SSH",
"global_settings_unknown_setting_from_settings_file": "Chave descoñecida nos axustes: '{setting_key}', descártaa e gárdaa en /etc/yunohost/settings-unknown.json", "global_settings_unknown_setting_from_settings_file": "Chave descoñecida nos axustes: '{setting_key}', descártaa e gárdaa en /etc/yunohost/settings-unknown.json",
"global_settings_setting_security_ssh_port": "Porto SSH",
"global_settings_setting_security_postfix_compatibility": "Compromiso entre compatibilidade e seguridade para o servidor Postfix. Aféctalle ao cifrado (e outros aspectos da seguridade)",
"global_settings_setting_security_ssh_compatibility": "Compromiso entre compatibilidade e seguridade para o servidor SSH. Aféctalle ao cifrado (e outros aspectos da seguridade)",
"global_settings_setting_security_password_user_strength": "Fortaleza do contrasinal da usuaria",
"global_settings_setting_security_password_admin_strength": "Fortaleza do contrasinal de Admin",
"global_settings_setting_security_nginx_compatibility": "Compromiso entre compatiblidade e seguridade para o servidor NGINX. Afecta ao cifrado (e outros aspectos relacionados coa seguridade)",
"global_settings_setting_pop3_enabled": "Activar protocolo POP3 no servidor de email",
"global_settings_reset_success": "Fíxose copia de apoio dos axustes en {path}", "global_settings_reset_success": "Fíxose copia de apoio dos axustes en {path}",
"global_settings_key_doesnt_exists": "O axuste '{settings_key}' non existe nos axustes globais, podes ver os valores dispoñibles executando 'yunohost settings list'", "global_settings_key_doesnt_exists": "O axuste '{settings_key}' non existe nos axustes globais, podes ver os valores dispoñibles executando 'yunohost settings list'",
"global_settings_cant_write_settings": "Non se gardou o ficheiro de configuración, razón: {reason}", "global_settings_cant_write_settings": "Non se gardou o ficheiro de configuración, razón: {reason}",
@ -336,11 +327,9 @@
"good_practices_about_user_password": "Vas definir o novo contrasinal de usuaria. O contrasinal debe ter 8 caracteres como mínimo—aínda que se recomenda utilizar un máis longo (ex. unha frase de paso) e/ou utilizar caracteres variados (maiúsculas, minúsculas, números e caracteres especiais).", "good_practices_about_user_password": "Vas definir o novo contrasinal de usuaria. O contrasinal debe ter 8 caracteres como mínimo—aínda que se recomenda utilizar un máis longo (ex. unha frase de paso) e/ou utilizar caracteres variados (maiúsculas, minúsculas, números e caracteres especiais).",
"good_practices_about_admin_password": "Vas definir o novo contrasinal de administración. O contrasinal debe ter 8 caracteres como mínimo—aínda que se recomenda utilizar un máis longo (ex. unha frase de paso) e/ou utilizar caracteres variados (maiúsculas, minúsculas, números e caracteres especiais).", "good_practices_about_admin_password": "Vas definir o novo contrasinal de administración. O contrasinal debe ter 8 caracteres como mínimo—aínda que se recomenda utilizar un máis longo (ex. unha frase de paso) e/ou utilizar caracteres variados (maiúsculas, minúsculas, números e caracteres especiais).",
"global_settings_unknown_type": "Situación non agardada, o axuste {setting} semella ter o tipo {unknown_type} pero non é un valor soportado polo sistema.", "global_settings_unknown_type": "Situación non agardada, o axuste {setting} semella ter o tipo {unknown_type} pero non é un valor soportado polo sistema.",
"global_settings_setting_backup_compress_tar_archives": "Ao crear novas copias de apoio, comprime os arquivos (.tar.gz) en lugar de non facelo (.tar). Nota: activando esta opción creas arquivos máis lixeiros, mais o procedemento da primeira copia será significativamente máis longo e esixente coa CPU.",
"global_settings_setting_smtp_relay_password": "Contrasinal no repetidor SMTP", "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_user": "Conta de usuaria no repetidor SMTP",
"global_settings_setting_smtp_relay_port": "Porto do 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.",
"group_updated": "Grupo '{group}' actualizado", "group_updated": "Grupo '{group}' actualizado",
"group_unknown": "Grupo descoñecido '{group}'", "group_unknown": "Grupo descoñecido '{group}'",
"group_deletion_failed": "Non se eliminou o grupo '{group}': {error}", "group_deletion_failed": "Non se eliminou o grupo '{group}': {error}",
@ -349,8 +338,6 @@
"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_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_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", "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_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", "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}'", "log_help_to_get_log": "Para ver o rexistro completo da operación '{desc}', usa o comando 'yunohost log show {name}'",
@ -543,7 +530,6 @@
"invalid_password": "Contrasinal non válido", "invalid_password": "Contrasinal non válido",
"ldap_server_is_down_restart_it": "O servidor LDAP está caído, intenta reinicialo...", "ldap_server_is_down_restart_it": "O servidor LDAP está caído, intenta reinicialo...",
"ldap_server_down": "Non se chegou ao servidor LDAP", "ldap_server_down": "Non se chegou ao servidor LDAP",
"global_settings_setting_security_experimental_enabled": "Activar características de seguridade experimentais (non actives isto se non sabes o que estás a facer!)",
"yunohost_postinstall_end_tip": "Post-install completada! Para rematar a configuración considera:\n- engadir unha primeira usuaria na sección 'Usuarias' na webadmin (ou 'yunohost user create <username>' na liña de comandos);\n- diagnosticar potenciais problemas na sección 'Diagnóstico' na webadmin (ou 'yunohost diagnosis run' na liña de comandos);\n- ler 'Rematando a configuración' e 'Coñece YunoHost' na documentación da administración: https://yunohost.org/admindoc.", "yunohost_postinstall_end_tip": "Post-install completada! Para rematar a configuración considera:\n- engadir unha primeira usuaria na sección 'Usuarias' na webadmin (ou 'yunohost user create <username>' na liña de comandos);\n- diagnosticar potenciais problemas na sección 'Diagnóstico' na webadmin (ou 'yunohost diagnosis run' na liña de comandos);\n- ler 'Rematando a configuración' e 'Coñece YunoHost' na documentación da administración: https://yunohost.org/admindoc.",
"yunohost_not_installed": "YunoHost non está instalado correctamente. Executa 'yunohost tools postinstall'", "yunohost_not_installed": "YunoHost non está instalado correctamente. Executa 'yunohost tools postinstall'",
"yunohost_installing": "Instalando YunoHost...", "yunohost_installing": "Instalando YunoHost...",
@ -592,7 +578,6 @@
"service_enabled": "O servizo '{service}' vai ser iniciado automáticamente no inicio do sistema.", "service_enabled": "O servizo '{service}' vai ser iniciado automáticamente no inicio do sistema.",
"diagnosis_apps_allgood": "Tódalas apps instaladas respectan as prácticas básicas de empaquetado", "diagnosis_apps_allgood": "Tódalas apps instaladas respectan as prácticas básicas de empaquetado",
"diagnosis_apps_bad_quality": "Esta aplicación está actualmente marcada como estragada no catálogo de aplicacións de YunoHost. Podería ser un problema temporal mentras as mantedoras intentan arranxar o problema. Ata ese momento a actualización desta app está desactivada.", "diagnosis_apps_bad_quality": "Esta aplicación está actualmente marcada como estragada no catálogo de aplicacións de YunoHost. Podería ser un problema temporal mentras as mantedoras intentan arranxar o problema. Ata ese momento a actualización desta app está desactivada.",
"global_settings_setting_security_nginx_redirect_to_https": "Redirixir peticións HTTP a HTTPs por defecto (NON DESACTIVAR ISTO a non ser que realmente saibas o que fas!)",
"log_user_import": "Importar usuarias", "log_user_import": "Importar usuarias",
"user_import_failed": "A operación de importación de usuarias fracasou", "user_import_failed": "A operación de importación de usuarias fracasou",
"user_import_missing_columns": "Faltan as seguintes columnas: {columns}", "user_import_missing_columns": "Faltan as seguintes columnas: {columns}",
@ -674,7 +659,6 @@
"migration_description_0021_migrate_to_bullseye": "Actualizar o sistema a Debian Bullseye e YunoHost 11.x", "migration_description_0021_migrate_to_bullseye": "Actualizar o sistema a Debian Bullseye e YunoHost 11.x",
"migration_0021_system_not_fully_up_to_date": "O teu sistema non está completamente actualizado. Fai unha actualización normal antes de executar a migración a Bullseye.", "migration_0021_system_not_fully_up_to_date": "O teu sistema non está completamente actualizado. Fai unha actualización normal antes de executar a migración a Bullseye.",
"migration_0021_general_warning": "Ten en conta que a migración é unha operación delicada. O equipo de YunoHost fixo todo o que puido para revisalo e probalo, pero aínda así poderían acontecer fallos no sistema ou apps.\n\nAsí as cousas, é recomendable:\n - Facer unha copia de apoio dos datos e apps importantes. Máis info en https://yunohost.org/backup;\n - Ter paciencia unha vez inicias a migración: dependendo da túa conexión a internet e hardware, podería levarlle varias horas completar o proceso.", "migration_0021_general_warning": "Ten en conta que a migración é unha operación delicada. O equipo de YunoHost fixo todo o que puido para revisalo e probalo, pero aínda así poderían acontecer fallos no sistema ou apps.\n\nAsí as cousas, é recomendable:\n - Facer unha copia de apoio dos datos e apps importantes. Máis info en https://yunohost.org/backup;\n - Ter paciencia unha vez inicias a migración: dependendo da túa conexión a internet e hardware, podería levarlle varias horas completar o proceso.",
"global_settings_setting_security_ssh_password_authentication": "Permitir autenticación con contrasinal para SSH",
"tools_upgrade_failed": "Non se actualizaron os paquetes: {packages_list}", "tools_upgrade_failed": "Non se actualizaron os paquetes: {packages_list}",
"migration_0023_not_enough_space": "Crear espazo suficiente en {path} para realizar a migración.", "migration_0023_not_enough_space": "Crear espazo suficiente en {path} para realizar a migración.",
"migration_0023_postgresql_11_not_installed": "PostgreSQL non estaba instalado no sistema. Nada que facer.", "migration_0023_postgresql_11_not_installed": "PostgreSQL non estaba instalado no sistema. Nada que facer.",
@ -684,6 +668,21 @@
"service_description_postgresql": "Almacena datos da app (Base datos SQL)", "service_description_postgresql": "Almacena datos da app (Base datos SQL)",
"tools_upgrade": "Actualizando paquetes do sistema", "tools_upgrade": "Actualizando paquetes do sistema",
"domain_config_default_app": "App por defecto", "domain_config_default_app": "App por defecto",
"global_settings_setting_backup_compress_tar_archives_help": "Ao crear novas copias de apoio, comprime os arquivos (.tar.gz) en lugar de non facelo (.tar). Nota: activando esta opción creas arquivos máis lixeiros, mais o procedemento da primeira copia será significativamente máis longo e esixente coa CPU.",
"global_settings_setting_security_experimental_enabled_help": "Activar características de seguridade experimentais (non actives isto se non sabes o que estás a facer!)",
"global_settings_setting_nginx_compatibility_help": "Compromiso entre compatiblidade e seguridade para o servidor NGINX. Afecta ao cifrado (e outros aspectos relacionados coa seguridade)",
"global_settings_setting_nginx_redirect_to_https_help": "Redirixir peticións HTTP a HTTPs por defecto (NON DESACTIVAR ISTO a non ser que realmente saibas o que fas!)",
"global_settings_setting_admin_strength": "Fortaleza do contrasinal de Admin",
"global_settings_setting_user_strength": "Fortaleza do contrasinal da usuaria",
"global_settings_setting_postfix_compatibility_help": "Compromiso entre compatibilidade e seguridade para o servidor Postfix. Aféctalle ao cifrado (e outros aspectos da seguridade)",
"global_settings_setting_ssh_compatibility_help": "Compromiso entre compatibilidade e seguridade para o servidor SSH. Aféctalle ao cifrado (e outros aspectos da seguridade)",
"global_settings_setting_ssh_password_authentication_help": "Permitir autenticación con contrasinal para SSH",
"global_settings_setting_ssh_port": "Porto SSH",
"global_settings_setting_webadmin_allowlist_help": "Enderezos IP con permiso para acceder á webadmin. Separados por vírgulas.",
"global_settings_setting_webadmin_allowlist_enabled_help": "Permitir que só algúns IPs accedan á webadmin.",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "Permitir o uso de DSA hostkey (en desuso) para a configuración do demoño SSH",
"global_settings_setting_smtp_allow_ipv6_help": "Permitir o uso de IPv6 para recibir e enviar emais",
"global_settings_setting_smtp_relay_enabled_help": "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.",
"migration_0024_rebuild_python_venv_broken_app": "Omitimos a app {app} porque virtualenv non se pode reconstruir para esta app. Deberías intentar resolver o problema forzando a actualización da app usando `yunohost app upgrade --force {app}`.", "migration_0024_rebuild_python_venv_broken_app": "Omitimos a app {app} porque virtualenv non se pode reconstruir para esta app. Deberías intentar resolver o problema forzando a actualización da app usando `yunohost app upgrade --force {app}`.",
"migration_0024_rebuild_python_venv_disclaimer_base": "Após a actualización a Debian Bullseye, algunhas aplicacións de Python precisan ser reconstruídas para usar a nova versión de Python que inclúe Debian (técnicamente: recrear o `virtualenv`). Mentras tanto, algunhas aplicacións de Python poderían non funcionar. YunoHost pode intentar reconstruir o virtualenv para algunhas, como se indica abaixo. Para outras, ou se falla a reconstrución, pode que teñas que forzar a actualización desas apps.", "migration_0024_rebuild_python_venv_disclaimer_base": "Após a actualización a Debian Bullseye, algunhas aplicacións de Python precisan ser reconstruídas para usar a nova versión de Python que inclúe Debian (técnicamente: recrear o `virtualenv`). Mentras tanto, algunhas aplicacións de Python poderían non funcionar. YunoHost pode intentar reconstruir o virtualenv para algunhas, como se indica abaixo. Para outras, ou se falla a reconstrución, pode que teñas que forzar a actualización desas apps.",
"migration_0024_rebuild_python_venv_disclaimer_rebuild": "Vaise intentar a reconstrución de virtualenv para as seguintes apps (Nota: a operación podería tomar algún tempo!): {rebuild_apps}", "migration_0024_rebuild_python_venv_disclaimer_rebuild": "Vaise intentar a reconstrución de virtualenv para as seguintes apps (Nota: a operación podería tomar algún tempo!): {rebuild_apps}",

View file

@ -232,18 +232,12 @@
"global_settings_key_doesnt_exists": "La chiave '{settings_key}' non esiste nelle impostazioni globali, puoi vedere tutte le chiavi disponibili eseguendo 'yunohost settings list'", "global_settings_key_doesnt_exists": "La chiave '{settings_key}' non esiste nelle impostazioni globali, puoi vedere tutte le chiavi disponibili eseguendo 'yunohost settings list'",
"global_settings_reset_success": "Le impostazioni precedenti sono state salvate in {path}", "global_settings_reset_success": "Le impostazioni precedenti sono state salvate in {path}",
"already_up_to_date": "Niente da fare. Tutto è già aggiornato.", "already_up_to_date": "Niente da fare. Tutto è già aggiornato.",
"global_settings_setting_security_nginx_compatibility": "Bilanciamento tra compatibilità e sicurezza per il server web NGIX. Riguarda gli algoritmi di cifratura (e altri aspetti legati alla sicurezza)",
"global_settings_setting_security_password_admin_strength": "Complessità della password di amministratore",
"global_settings_setting_security_password_user_strength": "Complessità della password utente",
"global_settings_setting_security_ssh_compatibility": "Bilanciamento tra compatibilità e sicurezza per il server SSH. Riguarda gli algoritmi di cifratura (e altri aspetti legati alla sicurezza)",
"global_settings_unknown_setting_from_settings_file": "Chiave sconosciuta nelle impostazioni: '{setting_key}', scartata e salvata in /etc/yunohost/settings-unknown.json", "global_settings_unknown_setting_from_settings_file": "Chiave sconosciuta nelle impostazioni: '{setting_key}', scartata e salvata in /etc/yunohost/settings-unknown.json",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Consenti l'uso del hostkey DSA (deprecato) per la configurazione del demone SSH",
"global_settings_unknown_type": "Situazione inaspettata, l'impostazione {setting} sembra essere di tipo {unknown_type} ma non è un tipo supportato dal sistema.", "global_settings_unknown_type": "Situazione inaspettata, l'impostazione {setting} sembra essere di tipo {unknown_type} ma non è un tipo supportato dal sistema.",
"good_practices_about_admin_password": "Stai per impostare una nuova password di amministratore. La password deve essere almeno di 8 caratteri - anche se è buona pratica utilizzare password più lunghe (es. una frase, una serie di parole) e/o utilizzare vari tipi di caratteri (maiuscole, minuscole, numeri e simboli).", "good_practices_about_admin_password": "Stai per impostare una nuova password di amministratore. La password deve essere almeno di 8 caratteri - anche se è buona pratica utilizzare password più lunghe (es. una frase, una serie di parole) e/o utilizzare vari tipi di caratteri (maiuscole, minuscole, numeri e simboli).",
"log_corrupted_md_file": "Il file dei metadati YAML associato con i registri è danneggiato: '{md_file}'\nErrore: {error}", "log_corrupted_md_file": "Il file dei metadati YAML associato con i registri è danneggiato: '{md_file}'\nErrore: {error}",
"log_link_to_log": "Registro completo di questa operazione: '<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>'", "log_link_to_log": "Registro completo di questa operazione: '<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>'",
"log_help_to_get_log": "Per vedere il registro dell'operazione '{desc}', usa il comando 'yunohost log show {name}'", "log_help_to_get_log": "Per vedere il registro dell'operazione '{desc}', usa il comando 'yunohost log show {name}'",
"global_settings_setting_security_postfix_compatibility": "Bilanciamento tra compatibilità e sicurezza per il server Postfix. Riguarda gli algoritmi di cifratura (e altri aspetti legati alla sicurezza)",
"log_link_to_failed_log": "Impossibile completare l'operazione '{desc}'! Per ricevere aiuto, per favore fornisci il registro completo dell'operazione <a href=\"#/tools/logs/{name}\">cliccando qui</a>", "log_link_to_failed_log": "Impossibile completare l'operazione '{desc}'! Per ricevere aiuto, per favore fornisci il registro completo dell'operazione <a href=\"#/tools/logs/{name}\">cliccando qui</a>",
"log_help_to_get_failed_log": "L'operazione '{desc}' non può essere completata. Per ottenere aiuto, per favore condividi il registro completo dell'operazione utilizzando il comando 'yunohost log share {name}'", "log_help_to_get_failed_log": "L'operazione '{desc}' non può essere completata. Per ottenere aiuto, per favore condividi il registro completo dell'operazione utilizzando il comando 'yunohost log share {name}'",
"log_does_exists": "Non esiste nessun registro delle operazioni chiamato '{log}', usa 'yunohost log list' per vedere tutti i registri delle operazioni disponibili", "log_does_exists": "Non esiste nessun registro delle operazioni chiamato '{log}', usa 'yunohost log list' per vedere tutti i registri delle operazioni disponibili",
@ -506,13 +500,9 @@
"group_already_exist_on_system_but_removing_it": "Il gruppo {group} esiste già tra i gruppi di sistema, ma YunoHost lo cancellerà...", "group_already_exist_on_system_but_removing_it": "Il gruppo {group} esiste già tra i gruppi di sistema, ma YunoHost lo cancellerà...",
"group_already_exist_on_system": "Il gruppo {group} esiste già tra i gruppi di sistema", "group_already_exist_on_system": "Il gruppo {group} esiste già tra i gruppi di sistema",
"group_already_exist": "Il gruppo {group} esiste già", "group_already_exist": "Il gruppo {group} esiste già",
"global_settings_setting_backup_compress_tar_archives": "Quando creo nuovi backup, usa un archivio (.tar.gz) al posto di un archivio non compresso (.tar). NB: abilitare quest'opzione significa create backup più leggeri, ma la procedura durerà di più e il carico CPU sarà maggiore.",
"global_settings_setting_smtp_relay_password": "Password del relay SMTP", "global_settings_setting_smtp_relay_password": "Password del relay SMTP",
"global_settings_setting_smtp_relay_user": "User account del relay SMTP", "global_settings_setting_smtp_relay_user": "User account del relay SMTP",
"global_settings_setting_smtp_relay_port": "Porta del relay SMTP", "global_settings_setting_smtp_relay_port": "Porta del relay SMTP",
"global_settings_setting_smtp_relay_host": "Utilizza SMTP relay per inviare mail al posto di questa instanza yunohost. Utile se sei in una di queste situazioni: la tua porta 25 è bloccata dal tuo provider ISP o VPS; hai un IP residenziale listato su DUHL; non sei puoi configurare il DNS inverso; oppure questo server non è direttamente esposto a Internet e vuoi usarne un'altro per spedire email.",
"global_settings_setting_smtp_allow_ipv6": "Permetti l'utilizzo di IPv6 per ricevere e inviare mail",
"global_settings_setting_pop3_enabled": "Abilita il protocollo POP3 per il server mail",
"dyndns_provider_unreachable": "Incapace di raggiungere il provider DynDNS {provider}: o il tuo YunoHost non è connesso ad internet o il server dynette è down.", "dyndns_provider_unreachable": "Incapace di raggiungere il provider DynDNS {provider}: o il tuo YunoHost non è connesso ad internet o il server dynette è down.",
"dpkg_lock_not_available": "Impossibile eseguire il comando in questo momento perché un altro programma sta bloccando dpkg (il package manager di sistema)", "dpkg_lock_not_available": "Impossibile eseguire il comando in questo momento perché un altro programma sta bloccando dpkg (il package manager di sistema)",
"domain_cannot_remove_main_add_new_one": "Non puoi rimuovere '{domain}' visto che è il dominio principale nonché il tuo unico dominio, devi prima aggiungere un altro dominio eseguendo 'yunohost domain add <altro-dominio.com>', impostarlo come dominio principale con 'yunohost domain main-domain n <altro-dominio.com>', e solo allora potrai rimuovere il dominio '{domain}' eseguendo 'yunohost domain remove {domain}'.'", "domain_cannot_remove_main_add_new_one": "Non puoi rimuovere '{domain}' visto che è il dominio principale nonché il tuo unico dominio, devi prima aggiungere un altro dominio eseguendo 'yunohost domain add <altro-dominio.com>', impostarlo come dominio principale con 'yunohost domain main-domain n <altro-dominio.com>', e solo allora potrai rimuovere il dominio '{domain}' eseguendo 'yunohost domain remove {domain}'.'",
@ -574,20 +564,16 @@
"migration_ldap_backup_before_migration": "Sto generando il backup del database LDAP e delle impostazioni delle app prima di effettuare la migrazione.", "migration_ldap_backup_before_migration": "Sto generando il backup del database LDAP e delle impostazioni delle app prima di effettuare la migrazione.",
"log_backup_create": "Crea un archivio backup", "log_backup_create": "Crea un archivio backup",
"global_settings_setting_ssowat_panel_overlay_enabled": "Abilita il pannello sovrapposto SSOwat", "global_settings_setting_ssowat_panel_overlay_enabled": "Abilita il pannello sovrapposto SSOwat",
"global_settings_setting_security_ssh_port": "Porta SSH",
"diagnosis_sshd_config_inconsistent_details": "Esegui <cmd>yunohost settings set security.ssh.port -v PORTA_SSH</cmd> per definire la porta SSH, e controlla con <cmd>yunohost tools regen-conf ssh --dry-run --with-diff</cmd>, poi <cmd>yunohost tools regen-conf ssh --force</cmd> per resettare la tua configurazione con le raccomandazioni YunoHost.", "diagnosis_sshd_config_inconsistent_details": "Esegui <cmd>yunohost settings set security.ssh.port -v PORTA_SSH</cmd> per definire la porta SSH, e controlla con <cmd>yunohost tools regen-conf ssh --dry-run --with-diff</cmd>, poi <cmd>yunohost tools regen-conf ssh --force</cmd> per resettare la tua configurazione con le raccomandazioni YunoHost.",
"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_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.", "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.", "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_update": "Non c'è abbastanza spazio libero per aggiornare questa applicazione",
"disk_space_not_sufficient_install": "Non c'è abbastanza spazio libero per installare questa applicazione", "disk_space_not_sufficient_install": "Non c'è abbastanza spazio libero per installare questa applicazione",
"app_config_unable_to_apply": "Applicazione dei valori nel pannello di configurazione non riuscita.", "app_config_unable_to_apply": "Applicazione dei valori nel pannello di configurazione non riuscita.",
"app_config_unable_to_read": "Lettura dei valori nel pannello di configurazione non riuscita.", "app_config_unable_to_read": "Lettura dei valori nel pannello di configurazione non riuscita.",
"diagnosis_apps_issue": "È stato rilevato un errore per lapp {app}", "diagnosis_apps_issue": "È stato rilevato un errore per lapp {app}",
"global_settings_setting_security_nginx_redirect_to_https": "Reindirizza richieste HTTP a HTTPs di default (NON DISABILITARE a meno che tu non sappia veramente bene cosa stai facendo!)",
"diagnosis_http_special_use_tld": "Il dominio {domain} è basato su un dominio di primo livello (TLD) dalluso speciale, come .local o .test, perciò non è previsto che sia esposto al di fuori della rete locale.", "diagnosis_http_special_use_tld": "Il dominio {domain} è basato su un dominio di primo livello (TLD) dalluso speciale, come .local o .test, perciò non è previsto che sia esposto al di fuori della rete locale.",
"domain_dns_conf_special_use_tld": "Questo dominio è basato su un dominio di primo livello (TLD) dalluso speciale, come .local o .test, perciò non è previsto abbia reali record DNS.", "domain_dns_conf_special_use_tld": "Questo dominio è basato su un dominio di primo livello (TLD) dalluso speciale, come .local o .test, perciò non è previsto abbia reali record DNS.",
"domain_dns_push_not_applicable": "La configurazione automatica del DNS non è applicabile al dominio {domain}. Dovresti configurare i tuoi record DNS manualmente, seguendo la documentazione su https://yunohost.org/dns_config.", "domain_dns_push_not_applicable": "La configurazione automatica del DNS non è applicabile al dominio {domain}. Dovresti configurare i tuoi record DNS manualmente, seguendo la documentazione su https://yunohost.org/dns_config.",
@ -615,7 +601,6 @@
"diagnosis_apps_allgood": "Tutte le applicazioni installate rispettano le pratiche di packaging di base", "diagnosis_apps_allgood": "Tutte le applicazioni installate rispettano le pratiche di packaging di base",
"config_apply_failed": "Lapplicazione della nuova configurazione è fallita: {error}", "config_apply_failed": "Lapplicazione della nuova configurazione è fallita: {error}",
"diagnosis_apps_outdated_ynh_requirement": "La versione installata di questapp richiede esclusivamente YunoHost >= 2.x, che tendenzialmente significa che non è aggiornata secondo le pratiche di packaging raccomandate. Dovresti proprio considerare di aggiornarla.", "diagnosis_apps_outdated_ynh_requirement": "La versione installata di questapp richiede esclusivamente YunoHost >= 2.x, che tendenzialmente significa che non è aggiornata secondo le pratiche di packaging raccomandate. Dovresti proprio considerare di aggiornarla.",
"global_settings_setting_security_experimental_enabled": "Abilita funzionalità di sicurezza sperimentali (non abilitare se non sai cosa stai facendo!)",
"invalid_number_min": "Deve essere più grande di {min}", "invalid_number_min": "Deve essere più grande di {min}",
"invalid_number_max": "Deve essere meno di {max}", "invalid_number_max": "Deve essere meno di {max}",
"log_app_config_set": "Applica la configurazione allapp '{}'", "log_app_config_set": "Applica la configurazione allapp '{}'",
@ -660,5 +645,19 @@
"config_validate_url": "È necessario inserire un URL web valido", "config_validate_url": "È necessario inserire un URL web valido",
"ldap_server_down": "Impossibile raggiungere il server LDAP", "ldap_server_down": "Impossibile raggiungere il server LDAP",
"ldap_server_is_down_restart_it": "Il servizio LDAP è down, prova a riavviarlo…", "ldap_server_is_down_restart_it": "Il servizio LDAP è down, prova a riavviarlo…",
"global_settings_setting_backup_compress_tar_archives_help": "Quando creo nuovi backup, usa un archivio (.tar.gz) al posto di un archivio non compresso (.tar). NB: abilitare quest'opzione significa create backup più leggeri, ma la procedura durerà di più e il carico CPU sarà maggiore.",
"global_settings_setting_security_experimental_enabled_help": "Abilita funzionalità di sicurezza sperimentali (non abilitare se non sai cosa stai facendo!)",
"global_settings_setting_nginx_compatibility_help": "Bilanciamento tra compatibilità e sicurezza per il server web NGIX. Riguarda gli algoritmi di cifratura (e altri aspetti legati alla sicurezza)",
"global_settings_setting_nginx_redirect_to_https_help": "Reindirizza richieste HTTP a HTTPs di default (NON DISABILITARE a meno che tu non sappia veramente bene cosa stai facendo!)",
"global_settings_setting_admin_strength": "Complessità della password di amministratore",
"global_settings_setting_user_strength": "Complessità della password utente",
"global_settings_setting_postfix_compatibility_help": "Bilanciamento tra compatibilità e sicurezza per il server Postfix. Riguarda gli algoritmi di cifratura (e altri aspetti legati alla sicurezza)",
"global_settings_setting_ssh_compatibility_help": "Bilanciamento tra compatibilità e sicurezza per il server SSH. Riguarda gli algoritmi di cifratura (e altri aspetti legati alla sicurezza)",
"global_settings_setting_ssh_port": "Porta SSH",
"global_settings_setting_webadmin_allowlist_help": "Indirizzi IP con il permesso di accedere al webadmin, separati da virgola.",
"global_settings_setting_webadmin_allowlist_enabled_help": "Permetti solo ad alcuni IP di accedere al webadmin.",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "Consenti l'uso del hostkey DSA (deprecato) per la configurazione del demone SSH",
"global_settings_setting_smtp_allow_ipv6_help": "Permetti l'utilizzo di IPv6 per ricevere e inviare mail",
"global_settings_setting_smtp_relay_enabled_help": "Utilizza SMTP relay per inviare mail al posto di questa instanza yunohost. Utile se sei in una di queste situazioni: la tua porta 25 è bloccata dal tuo provider ISP o VPS; hai un IP residenziale listato su DUHL; non sei puoi configurare il DNS inverso; oppure questo server non è direttamente esposto a Internet e vuoi usarne un'altro per spedire email.",
"domain_config_default_app": "Applicazione di default" "domain_config_default_app": "Applicazione di default"
} }

View file

@ -86,9 +86,7 @@
"dyndns_key_generating": "Oppretter DNS-nøkkel… Dette kan ta en stund.", "dyndns_key_generating": "Oppretter DNS-nøkkel… Dette kan ta en stund.",
"dyndns_no_domain_registered": "Inget domene registrert med DynDNS", "dyndns_no_domain_registered": "Inget domene registrert med DynDNS",
"dyndns_registered": "DynDNS-domene registrert", "dyndns_registered": "DynDNS-domene registrert",
"global_settings_setting_security_password_admin_strength": "Admin-passordets styrke",
"dyndns_registration_failed": "Kunne ikke registrere DynDNS-domene: {error}", "dyndns_registration_failed": "Kunne ikke registrere DynDNS-domene: {error}",
"global_settings_setting_security_password_user_strength": "Brukerpassordets styrke",
"log_backup_restore_app": "Gjenopprett '{}' fra sikkerhetskopiarkiv", "log_backup_restore_app": "Gjenopprett '{}' fra sikkerhetskopiarkiv",
"log_remove_on_failed_install": "Fjern '{}' etter mislykket installasjon", "log_remove_on_failed_install": "Fjern '{}' etter mislykket installasjon",
"log_selfsigned_cert_install": "Installer selvsignert sertifikat på '{}'-domenet", "log_selfsigned_cert_install": "Installer selvsignert sertifikat på '{}'-domenet",
@ -115,5 +113,7 @@
"log_help_to_get_log": "For å vise loggen for operasjonen '{desc}', bruk kommandoen 'yunohost log show {name}'", "log_help_to_get_log": "For å vise loggen for operasjonen '{desc}', bruk kommandoen 'yunohost log show {name}'",
"log_user_create": "Legg til '{}' bruker", "log_user_create": "Legg til '{}' bruker",
"app_change_url_success": "{app} nettadressen er nå {domain}{path}", "app_change_url_success": "{app} nettadressen er nå {domain}{path}",
"app_install_failed": "Kunne ikke installere {app}: {error}" "app_install_failed": "Kunne ikke installere {app}: {error}",
"global_settings_setting_admin_strength": "Admin-passordets styrke",
"global_settings_setting_user_strength": "Brukerpassordets styrke"
} }

View file

@ -293,8 +293,6 @@
"backup_mount_archive_for_restore": "Preparacion de larchiu per restauracion...", "backup_mount_archive_for_restore": "Preparacion de larchiu per restauracion...",
"dyndns_could_not_check_available": "Verificacion impossibla de la disponibilitat de {domain} sus {provider}.", "dyndns_could_not_check_available": "Verificacion impossibla de la disponibilitat de {domain} sus {provider}.",
"file_does_not_exist": "Lo camin {path} existís pas.", "file_does_not_exist": "Lo camin {path} existís pas.",
"global_settings_setting_security_password_admin_strength": "Fòrça del senhal administrator",
"global_settings_setting_security_password_user_strength": "Fòrça del senhal utilizaire",
"root_password_replaced_by_admin_password": "Lo senhal root es estat remplaçat pel senhal administrator.", "root_password_replaced_by_admin_password": "Lo senhal root es estat remplaçat pel senhal administrator.",
"service_restarted": "Lo servici '{service}' es estat reaviat", "service_restarted": "Lo servici '{service}' es estat reaviat",
"admin_password_too_long": "Causissètz un senhal dalmens 127 caractèrs", "admin_password_too_long": "Causissètz un senhal dalmens 127 caractèrs",
@ -308,7 +306,6 @@
"log_regen_conf": "Regenerar las configuracions del sistèma « {} »", "log_regen_conf": "Regenerar las configuracions del sistèma « {} »",
"service_reloaded_or_restarted": "Lo servici « {service} » es estat recargat o reaviat", "service_reloaded_or_restarted": "Lo servici « {service} » es estat recargat o reaviat",
"dpkg_is_broken": "Podètz pas far aquò pel moment perque dpkg/APT (los gestionaris de paquets del sistèma) sembla èsser mal configurat… Podètz ensajar de solucionar aquò en vos connectar via SSH e en executar «sudo dpkg --configure -a».", "dpkg_is_broken": "Podètz pas far aquò pel moment perque dpkg/APT (los gestionaris de paquets del sistèma) sembla èsser mal configurat… Podètz ensajar de solucionar aquò en vos connectar via SSH e en executar «sudo dpkg --configure -a».",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Autorizar lutilizacion de la clau òst DSA (obsolèta) per la configuracion del servici SSH",
"hook_json_return_error": "Fracàs de la lectura del retorn de lscript {path}. Error : {msg}. Contengut brut: {raw_content}", "hook_json_return_error": "Fracàs de la lectura del retorn de lscript {path}. Error : {msg}. Contengut brut: {raw_content}",
"pattern_password_app": "O planhèm, los senhals devon pas conténer los caractèrs seguents : {forbidden_chars}", "pattern_password_app": "O planhèm, los senhals devon pas conténer los caractèrs seguents : {forbidden_chars}",
"regenconf_file_backed_up": "Lo fichièr de configuracion « {conf} » es estat salvagardat dins « {backup} »", "regenconf_file_backed_up": "Lo fichièr de configuracion « {conf} » es estat salvagardat dins « {backup} »",
@ -325,9 +322,6 @@
"regenconf_dry_pending_applying": "Verificacion de la configuracion que seriá estada aplicada a la categoria « {category} »…", "regenconf_dry_pending_applying": "Verificacion de la configuracion que seriá estada aplicada a la categoria « {category} »…",
"regenconf_failed": "Regeneracion impossibla de la configuracion per la(s) categoria(s) : {categories}", "regenconf_failed": "Regeneracion impossibla de la configuracion per la(s) categoria(s) : {categories}",
"regenconf_pending_applying": "Aplicacion de la configuracion en espèra per la categoria « {category} »…", "regenconf_pending_applying": "Aplicacion de la configuracion en espèra per la categoria « {category} »…",
"global_settings_setting_security_nginx_compatibility": "Solucion de compromés entre compatibilitat e seguretat pel servidor web NGINX Afècta los criptografs (e dautres aspèctes ligats amb la seguretat)",
"global_settings_setting_security_ssh_compatibility": "Solucion de compromés entre compatibilitat e seguretat pel servidor SSH. Afècta los criptografs (e dautres aspèctes ligats amb la seguretat)",
"global_settings_setting_security_postfix_compatibility": "Solucion de compromés entre compatibilitat e seguretat pel servidor Postfix. Afècta los criptografs (e dautres aspèctes ligats amb la seguretat)",
"service_reload_failed": "Impossible de recargar lo servici « {service} »\n\nJornal daudit recent: {logs}", "service_reload_failed": "Impossible de recargar lo servici « {service} »\n\nJornal daudit recent: {logs}",
"service_restart_failed": "Impossible de reaviar lo servici « {service} »\n\nJornal daudit recent: {logs}", "service_restart_failed": "Impossible de reaviar lo servici « {service} »\n\nJornal daudit recent: {logs}",
"service_reload_or_restart_failed": "Impossible de recargar o reaviar lo servici « {service} »\n\nJornal daudit recent: {logs}", "service_reload_or_restart_failed": "Impossible de recargar o reaviar lo servici « {service} »\n\nJornal daudit recent: {logs}",
@ -453,7 +447,6 @@
"diagnosis_ip_broken_resolvconf": "La resolucion del nom de domeni sembla copada sul servidor, poiriá èsser ligada al fait que <code>/etc/resolv.conf</code> manda pas a <code>127.0.0.1</code>.", "diagnosis_ip_broken_resolvconf": "La resolucion del nom de domeni sembla copada sul servidor, poiriá èsser ligada al fait que <code>/etc/resolv.conf</code> manda pas a <code>127.0.0.1</code>.",
"diagnosis_ip_weird_resolvconf": "La resolucion del nom de domeni sembla foncionar, mas sembla quutiilizatz un fichièr <code>/etc/resolv.conf</code> personalizat.", "diagnosis_ip_weird_resolvconf": "La resolucion del nom de domeni sembla foncionar, mas sembla quutiilizatz un fichièr <code>/etc/resolv.conf</code> personalizat.",
"diagnosis_diskusage_verylow": "Lo lòc demmagazinatge <code>{mountpoint}</code> (sul periferic <code>{device}</code>) a solament {free} ({free_percent}%). Deuriatz considerar de liberar un pauc despaci.", "diagnosis_diskusage_verylow": "Lo lòc demmagazinatge <code>{mountpoint}</code> (sul periferic <code>{device}</code>) a solament {free} ({free_percent}%). Deuriatz considerar de liberar un pauc despaci.",
"global_settings_setting_pop3_enabled": "Activar lo protocòl POP3 pel servidor de corrièr",
"diagnosis_diskusage_ok": "Lo lòc demmagazinatge <code>{mountpoint}</code> (sul periferic <code>{device}</code>) a encara {free} ({free_percent}%) de liure!", "diagnosis_diskusage_ok": "Lo lòc demmagazinatge <code>{mountpoint}</code> (sul periferic <code>{device}</code>) a encara {free} ({free_percent}%) de liure!",
"diagnosis_swap_none": "Lo sistèma a pas cap de memòria descambi. Auriatz de considerar dajustar almens {recommended} descambi per evitar las situacions ont lo sistèma manca de memòria.", "diagnosis_swap_none": "Lo sistèma a pas cap de memòria descambi. Auriatz de considerar dajustar almens {recommended} descambi per evitar las situacions ont lo sistèma manca de memòria.",
"diagnosis_swap_notsomuch": "Lo sistèma a solament {total} de memòria descambi. Auriatz de considerar dajustar almens {recommended} descambi per evitar las situacions ont lo sistèma manca de memòria.", "diagnosis_swap_notsomuch": "Lo sistèma a solament {total} de memòria descambi. Auriatz de considerar dajustar almens {recommended} descambi per evitar las situacions ont lo sistèma manca de memòria.",
@ -488,5 +481,11 @@
"diagnosis_domain_not_found_details": "Lo domeni {domain} existís pas a la basa de donadas WHOIS o a expirat!", "diagnosis_domain_not_found_details": "Lo domeni {domain} existís pas a la basa de donadas WHOIS o a expirat!",
"diagnosis_domain_expiration_not_found": "Impossible de verificar la data dexpiracion dunes domenis", "diagnosis_domain_expiration_not_found": "Impossible de verificar la data dexpiracion dunes domenis",
"backup_create_size_estimation": "Larchiu contendrà apraquí {size} de donadas.", "backup_create_size_estimation": "Larchiu contendrà apraquí {size} de donadas.",
"app_restore_script_failed": "Una error ses producha a linterior del script de restauracion de laplicacion" "app_restore_script_failed": "Una error ses producha a linterior del script de restauracion de laplicacion",
"global_settings_setting_nginx_compatibility_help": "Solucion de compromés entre compatibilitat e seguretat pel servidor web NGINX Afècta los criptografs (e dautres aspèctes ligats amb la seguretat)",
"global_settings_setting_admin_strength": "Fòrça del senhal administrator",
"global_settings_setting_user_strength": "Fòrça del senhal utilizaire",
"global_settings_setting_postfix_compatibility_help": "Solucion de compromés entre compatibilitat e seguretat pel servidor Postfix. Afècta los criptografs (e dautres aspèctes ligats amb la seguretat)",
"global_settings_setting_ssh_compatibility_help": "Solucion de compromés entre compatibilitat e seguretat pel servidor SSH. Afècta los criptografs (e dautres aspèctes ligats amb la seguretat)",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "Autorizar lutilizacion de la clau òst DSA (obsolèta) per la configuracion del servici SSH"
} }

View file

@ -238,7 +238,6 @@
"regenconf_file_removed": "Файл конфигурации '{conf}' удален", "regenconf_file_removed": "Файл конфигурации '{conf}' удален",
"permission_not_found": "Разрешение '{permission}' не найдено", "permission_not_found": "Разрешение '{permission}' не найдено",
"group_cannot_edit_all_users": "Группа 'all_users' не может быть отредактирована вручную. Это специальная группа, предназначенная для всех пользователей, зарегистрированных в YunoHost", "group_cannot_edit_all_users": "Группа 'all_users' не может быть отредактирована вручную. Это специальная группа, предназначенная для всех пользователей, зарегистрированных в YunoHost",
"global_settings_setting_smtp_allow_ipv6": "Разрешить использование IPv6 для получения и отправки почты",
"log_dyndns_subscribe": "Подписаться на субдомен YunoHost '{}'", "log_dyndns_subscribe": "Подписаться на субдомен YunoHost '{}'",
"pattern_firstname": "Должно быть настоящее имя", "pattern_firstname": "Должно быть настоящее имя",
"migrations_pending_cant_rerun": "Эти миграции еще не завершены, поэтому не могут быть запущены снова: {ids}", "migrations_pending_cant_rerun": "Эти миграции еще не завершены, поэтому не могут быть запущены снова: {ids}",
@ -269,8 +268,6 @@
"group_cannot_be_deleted": "Группа {group} не может быть удалена вручную.", "group_cannot_be_deleted": "Группа {group} не может быть удалена вручную.",
"log_app_config_set": "Примените конфигурацию приложения '{}'", "log_app_config_set": "Примените конфигурацию приложения '{}'",
"log_backup_restore_app": "Восстановление '{}' из резервной копии", "log_backup_restore_app": "Восстановление '{}' из резервной копии",
"global_settings_setting_security_webadmin_allowlist": "IP-адреса, разрешенные для доступа к веб-интерфейсу администратора. Разделенные запятыми.",
"global_settings_setting_security_webadmin_allowlist_enabled": "Разрешите доступ к веб-интерфейсу администратора только некоторым IP-адресам.",
"log_domain_remove": "Удалить домен '{}' из конфигурации системы", "log_domain_remove": "Удалить домен '{}' из конфигурации системы",
"user_import_success": "Пользователи успешно импортированы", "user_import_success": "Пользователи успешно импортированы",
"group_user_already_in_group": "Пользователь {user} уже входит в группу {group}", "group_user_already_in_group": "Пользователь {user} уже входит в группу {group}",
@ -284,7 +281,6 @@
"diagnosis_sshd_config_inconsistent_details": "Пожалуйста, выполните <cmd>yunohost settings set security.ssh.port -v YOUR_SSH_PORT</cmd>, чтобы определить порт SSH, и проверьте <cmd>yunohost tools regen-conf ssh --dry-run --with-diff</cmd> и <cmd>yunohost tools regen-conf ssh --force</cmd>, чтобы сбросить ваш conf в соответствии с рекомендациями YunoHost.", "diagnosis_sshd_config_inconsistent_details": "Пожалуйста, выполните <cmd>yunohost settings set security.ssh.port -v YOUR_SSH_PORT</cmd>, чтобы определить порт SSH, и проверьте <cmd>yunohost tools regen-conf ssh --dry-run --with-diff</cmd> и <cmd>yunohost tools regen-conf ssh --force</cmd>, чтобы сбросить ваш conf в соответствии с рекомендациями YunoHost.",
"log_domain_main_domain": "Сделать '{}' основным доменом", "log_domain_main_domain": "Сделать '{}' основным доменом",
"diagnosis_sshd_config_insecure": "Похоже, что конфигурация SSH была изменена вручную, и она небезопасна, поскольку не содержит директив 'AllowGroups' или 'AllowUsers' для ограничения доступа авторизованных пользователей.", "diagnosis_sshd_config_insecure": "Похоже, что конфигурация SSH была изменена вручную, и она небезопасна, поскольку не содержит директив 'AllowGroups' или 'AllowUsers' для ограничения доступа авторизованных пользователей.",
"global_settings_setting_security_ssh_port": "SSH порт",
"group_already_exist_on_system": "Группа {group} уже существует в системных группах", "group_already_exist_on_system": "Группа {group} уже существует в системных группах",
"group_already_exist_on_system_but_removing_it": "Группа {group} уже существует в системных группах, но YunoHost удалит ее...", "group_already_exist_on_system_but_removing_it": "Группа {group} уже существует в системных группах, но YunoHost удалит ее...",
"group_unknown": "Группа '{group}' неизвестна", "group_unknown": "Группа '{group}' неизвестна",
@ -303,7 +299,6 @@
"regenconf_failed": "Не удалось восстановить конфигурацию для категории(й): {categories}", "regenconf_failed": "Не удалось восстановить конфигурацию для категории(й): {categories}",
"diagnosis_services_conf_broken": "Конфигурация нарушена для службы {service}!", "diagnosis_services_conf_broken": "Конфигурация нарушена для службы {service}!",
"diagnosis_sshd_config_inconsistent": "Похоже, что порт SSH был вручную изменен в /etc/ssh/sshd_config. Начиная с версии YunoHost 4.2, доступен новый глобальный параметр 'security.ssh.port', позволяющий избежать ручного редактирования конфигурации.", "diagnosis_sshd_config_inconsistent": "Похоже, что порт SSH был вручную изменен в /etc/ssh/sshd_config. Начиная с версии YunoHost 4.2, доступен новый глобальный параметр 'security.ssh.port', позволяющий избежать ручного редактирования конфигурации.",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Разрешить использование (устаревшего) ключа хоста DSA для конфигурации демона SSH",
"hook_exec_not_terminated": "Скрипт не завершился должным образом: {path}", "hook_exec_not_terminated": "Скрипт не завершился должным образом: {path}",
"ip6tables_unavailable": "Вы не можете играть с ip6tables здесь. Либо Вы находитесь в контейнере, либо ваше ядро это не поддерживает", "ip6tables_unavailable": "Вы не можете играть с ip6tables здесь. Либо Вы находитесь в контейнере, либо ваше ядро это не поддерживает",
"iptables_unavailable": "Вы не можете играть с ip6tables здесь. Либо Вы находитесь в контейнере, либо ваше ядро это не поддерживает", "iptables_unavailable": "Вы не можете играть с ip6tables здесь. Либо Вы находитесь в контейнере, либо ваше ядро это не поддерживает",
@ -334,5 +329,11 @@
"log_app_remove": "Удалите приложение '{}'", "log_app_remove": "Удалите приложение '{}'",
"not_enough_disk_space": "Недостаточно свободного места в '{path}'", "not_enough_disk_space": "Недостаточно свободного места в '{path}'",
"pattern_email_forward": "Должен быть корректный адрес электронной почты, символ '+' допустим (например, someone+tag@example.com)", "pattern_email_forward": "Должен быть корректный адрес электронной почты, символ '+' допустим (например, someone+tag@example.com)",
"permission_deletion_failed": "Не удалось удалить разрешение '{permission}': {error}",
"global_settings_setting_ssh_port": "SSH порт",
"global_settings_setting_webadmin_allowlist_help": "IP-адреса, разрешенные для доступа к веб-интерфейсу администратора. Разделенные запятыми.",
"global_settings_setting_webadmin_allowlist_enabled_help": "Разрешите доступ к веб-интерфейсу администратора только некоторым IP-адресам.",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "Разрешить использование (устаревшего) ключа хоста DSA для конфигурации демона SSH",
"global_settings_setting_smtp_allow_ipv6_help": "Разрешить использование IPv6 для получения и отправки почты",
"permission_deletion_failed": "Не удалось удалить разрешение '{permission}': {error}" "permission_deletion_failed": "Не удалось удалить разрешение '{permission}': {error}"
} }

View file

@ -241,24 +241,11 @@
"good_practices_about_user_password": "Зараз ви збираєтеся поставити новий пароль користувача. Пароль повинен складатися не менше ніж з 8 символів, але хорошою практикою є використання більш довгого пароля (тобто гасла) і/або використання різних символів (великих, малих, цифр і спеціальних символів).", "good_practices_about_user_password": "Зараз ви збираєтеся поставити новий пароль користувача. Пароль повинен складатися не менше ніж з 8 символів, але хорошою практикою є використання більш довгого пароля (тобто гасла) і/або використання різних символів (великих, малих, цифр і спеціальних символів).",
"good_practices_about_admin_password": "Зараз ви збираєтеся поставити новий пароль адміністрації. Пароль повинен складатися не менше ніж з 8 символів, але хорошою практикою є використання більш довгого пароля (тобто парольного гасла) і/або використання різних символів (великих, малих, цифр і спеціальних символів).", "good_practices_about_admin_password": "Зараз ви збираєтеся поставити новий пароль адміністрації. Пароль повинен складатися не менше ніж з 8 символів, але хорошою практикою є використання більш довгого пароля (тобто парольного гасла) і/або використання різних символів (великих, малих, цифр і спеціальних символів).",
"global_settings_unknown_type": "Несподівана ситуація, налаштування {setting} має тип {unknown_type}, але це не тип, підтримуваний системою.", "global_settings_unknown_type": "Несподівана ситуація, налаштування {setting} має тип {unknown_type}, але це не тип, підтримуваний системою.",
"global_settings_setting_backup_compress_tar_archives": "При створенні нових резервних копій стискати архіви (.tar.gz) замість нестислих архівів (.tar). NB: вмикання цієї опції означає створення легших архівів резервних копій, але початкова процедура резервного копіювання буде значно довшою і важчою для CPU.",
"global_settings_setting_security_webadmin_allowlist": "IP-адреси, яким дозволений доступ до вебадміністрації. Через кому.",
"global_settings_setting_security_webadmin_allowlist_enabled": "Дозволити доступ до вебадміністрації тільки деяким IP-адресам.",
"global_settings_setting_smtp_relay_password": "Пароль хоста SMTP-ретрансляції", "global_settings_setting_smtp_relay_password": "Пароль хоста SMTP-ретрансляції",
"global_settings_setting_smtp_relay_user": "Обліковий запис користувача SMTP-ретрансляції", "global_settings_setting_smtp_relay_user": "Обліковий запис користувача SMTP-ретрансляції",
"global_settings_setting_smtp_relay_port": "Порт SMTP-ретрансляції", "global_settings_setting_smtp_relay_port": "Порт SMTP-ретрансляції",
"global_settings_setting_smtp_relay_host": "Хост SMTP-ретрансляції, який буде використовуватися для надсилання е-пошти замість цього зразка Yunohost. Корисно, якщо ви знаходитеся в одній із цих ситуацій: ваш 25 порт заблокований вашим провайдером або VPS провайдером, у вас є житловий IP в списку DUHL, ви не можете налаштувати зворотний DNS або цей сервер не доступний безпосередньо в Інтернеті і ви хочете використовувати інший сервер для відправки електронних листів.",
"global_settings_setting_smtp_allow_ipv6": "Дозволити використання IPv6 для отримання і надсилання листів е-пошти",
"global_settings_setting_ssowat_panel_overlay_enabled": "Увімкнути накладення панелі SSOwat", "global_settings_setting_ssowat_panel_overlay_enabled": "Увімкнути накладення панелі SSOwat",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Дозволити використання (застарілого) ключа DSA для конфігурації демона SSH",
"global_settings_unknown_setting_from_settings_file": "Невідомий ключ в налаштуваннях: '{setting_key}', відхиліть його і збережіть у /etc/yunohost/settings-unknown.json", "global_settings_unknown_setting_from_settings_file": "Невідомий ключ в налаштуваннях: '{setting_key}', відхиліть його і збережіть у /etc/yunohost/settings-unknown.json",
"global_settings_setting_security_ssh_port": "SSH-порт",
"global_settings_setting_security_postfix_compatibility": "Компроміс між сумісністю і безпекою для сервера Postfix. Впливає на шифри (і інші аспекти, пов'язані з безпекою)",
"global_settings_setting_security_ssh_compatibility": "Компроміс між сумісністю і безпекою для SSH-сервера. Впливає на шифри (і інші аспекти, пов'язані з безпекою)",
"global_settings_setting_security_password_user_strength": "Надійність пароля користувача",
"global_settings_setting_security_password_admin_strength": "Надійність пароля адміністратора",
"global_settings_setting_security_nginx_compatibility": "Компроміс між сумісністю і безпекою для вебсервера NGINX. Впливає на шифри (і інші аспекти, пов'язані з безпекою)",
"global_settings_setting_pop3_enabled": "Увімкніть протокол POP3 для поштового сервера",
"global_settings_reset_success": "Попередні налаштування тепер збережені в {path}", "global_settings_reset_success": "Попередні налаштування тепер збережені в {path}",
"global_settings_key_doesnt_exists": "Ключ '{settings_key}' не існує в глобальних налаштуваннях, ви можете побачити всі доступні ключі, виконавши команду 'yunohost settings list'", "global_settings_key_doesnt_exists": "Ключ '{settings_key}' не існує в глобальних налаштуваннях, ви можете побачити всі доступні ключі, виконавши команду 'yunohost settings list'",
"global_settings_cant_write_settings": "Неможливо зберегти файл налаштувань, причина: {reason}", "global_settings_cant_write_settings": "Неможливо зберегти файл налаштувань, причина: {reason}",
@ -598,7 +585,6 @@
"log_user_import": "Імпорт користувачів", "log_user_import": "Імпорт користувачів",
"ldap_server_is_down_restart_it": "Службу LDAP вимкнено, спробуйте перезапустити її...", "ldap_server_is_down_restart_it": "Службу LDAP вимкнено, спробуйте перезапустити її...",
"ldap_server_down": "Не вдається під'єднатися до сервера LDAP", "ldap_server_down": "Не вдається під'єднатися до сервера LDAP",
"global_settings_setting_security_experimental_enabled": "Увімкнути експериментальні функції безпеки (не вмикайте це, якщо ви не знаєте, що робите!)",
"diagnosis_apps_deprecated_practices": "Установлена версія цього застосунку все ще використовує деякі надзастарілі практики упакування. Вам дійсно варто подумати про його оновлення.", "diagnosis_apps_deprecated_practices": "Установлена версія цього застосунку все ще використовує деякі надзастарілі практики упакування. Вам дійсно варто подумати про його оновлення.",
"diagnosis_apps_outdated_ynh_requirement": "Установлена версія цього застосунку вимагає лише Yunohost >= 2.x, що, як правило, вказує на те, що воно не відповідає сучасним рекомендаційним практикам упакування та порадникам. Вам дійсно варто подумати про його оновлення.", "diagnosis_apps_outdated_ynh_requirement": "Установлена версія цього застосунку вимагає лише Yunohost >= 2.x, що, як правило, вказує на те, що воно не відповідає сучасним рекомендаційним практикам упакування та порадникам. Вам дійсно варто подумати про його оновлення.",
"diagnosis_apps_bad_quality": "Цей застосунок наразі позначено як зламаний у каталозі застосунків YunoHost. Це може бути тимчасовою проблемою, поки організатори намагаються вирішити цю проблему. Тим часом оновлення цього застосунку вимкнено.", "diagnosis_apps_bad_quality": "Цей застосунок наразі позначено як зламаний у каталозі застосунків YunoHost. Це може бути тимчасовою проблемою, поки організатори намагаються вирішити цю проблему. Тим часом оновлення цього застосунку вимкнено.",
@ -607,7 +593,6 @@
"diagnosis_apps_issue": "Виявлено проблему із застосунком {app}", "diagnosis_apps_issue": "Виявлено проблему із застосунком {app}",
"diagnosis_apps_allgood": "Усі встановлені застосунки дотримуються основних способів упакування", "diagnosis_apps_allgood": "Усі встановлені застосунки дотримуються основних способів упакування",
"diagnosis_high_number_auth_failures": "Останнім часом сталася підозріло велика кількість помилок автентифікації. Ви можете переконатися, що fail2ban працює і правильно налаштований, або скористатися власним портом для SSH, як описано в https://yunohost.org/security.", "diagnosis_high_number_auth_failures": "Останнім часом сталася підозріло велика кількість помилок автентифікації. Ви можете переконатися, що fail2ban працює і правильно налаштований, або скористатися власним портом для SSH, як описано в https://yunohost.org/security.",
"global_settings_setting_security_nginx_redirect_to_https": "Типово переспрямовувати HTTP-запити до HTTP (НЕ ВИМИКАЙТЕ, якщо ви дійсно не знаєте, що робите!)",
"app_config_unable_to_apply": "Не вдалося застосувати значення панелі конфігурації.", "app_config_unable_to_apply": "Не вдалося застосувати значення панелі конфігурації.",
"app_config_unable_to_read": "Не вдалося розпізнати значення панелі конфігурації.", "app_config_unable_to_read": "Не вдалося розпізнати значення панелі конфігурації.",
"config_apply_failed": "Не вдалося застосувати нову конфігурацію: {error}", "config_apply_failed": "Не вдалося застосувати нову конфігурацію: {error}",
@ -674,7 +659,6 @@
"migration_0021_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Bullseye.", "migration_0021_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Bullseye.",
"migration_0021_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частину системи або її застосунків.\n\nТому рекомендовано:\n - Виконати резервне копіювання всіх важливих даних або застосунків. Подробиці на сайті https://yunohost.org/backup; \n - Наберіться терпіння після запуску міграції: В залежності від вашого з'єднання з Інтернетом і апаратного забезпечення, оновлення може зайняти до декількох годин.", "migration_0021_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частину системи або її застосунків.\n\nТому рекомендовано:\n - Виконати резервне копіювання всіх важливих даних або застосунків. Подробиці на сайті https://yunohost.org/backup; \n - Наберіться терпіння після запуску міграції: В залежності від вашого з'єднання з Інтернетом і апаратного забезпечення, оновлення може зайняти до декількох годин.",
"migration_description_0021_migrate_to_bullseye": "Оновлення системи до Debian Bullseye і YunoHost 11.x", "migration_description_0021_migrate_to_bullseye": "Оновлення системи до Debian Bullseye і YunoHost 11.x",
"global_settings_setting_security_ssh_password_authentication": "Дозволити автентифікацію паролем для SSH",
"service_description_postgresql": "Зберігає дані застосунків (база даних SQL)", "service_description_postgresql": "Зберігає дані застосунків (база даних SQL)",
"domain_config_default_app": "Типовий застосунок", "domain_config_default_app": "Типовий застосунок",
"migration_0023_postgresql_13_not_installed": "PostgreSQL 11 встановлено, але не PostgreSQL 13!? У вашій системі могло статися щось неприємне :(...", "migration_0023_postgresql_13_not_installed": "PostgreSQL 11 встановлено, але не PostgreSQL 13!? У вашій системі могло статися щось неприємне :(...",
@ -684,6 +668,21 @@
"migration_0023_not_enough_space": "Звільніть достатньо місця в {path} для виконання міграції.", "migration_0023_not_enough_space": "Звільніть достатньо місця в {path} для виконання міграції.",
"migration_0023_postgresql_11_not_installed": "PostgreSQL не було встановлено у вашій системі. Нічого робити.", "migration_0023_postgresql_11_not_installed": "PostgreSQL не було встановлено у вашій системі. Нічого робити.",
"migration_description_0022_php73_to_php74_pools": "Перенесення конфігураційних файлів php7.3-fpm 'pool' на php7.4", "migration_description_0022_php73_to_php74_pools": "Перенесення конфігураційних файлів php7.3-fpm 'pool' на php7.4",
"global_settings_setting_backup_compress_tar_archives_help": "При створенні нових резервних копій стискати архіви (.tar.gz) замість нестислих архівів (.tar). NB: вмикання цієї опції означає створення легших архівів резервних копій, але початкова процедура резервного копіювання буде значно довшою і важчою для CPU.",
"global_settings_setting_security_experimental_enabled_help": "Увімкнути експериментальні функції безпеки (не вмикайте це, якщо ви не знаєте, що робите!)",
"global_settings_setting_nginx_compatibility_help": "Компроміс між сумісністю і безпекою для вебсервера NGINX. Впливає на шифри (і інші аспекти, пов'язані з безпекою)",
"global_settings_setting_nginx_redirect_to_https_help": "Типово переспрямовувати HTTP-запити до HTTP (НЕ ВИМИКАЙТЕ, якщо ви дійсно не знаєте, що робите!)",
"global_settings_setting_admin_strength": "Надійність пароля адміністратора",
"global_settings_setting_user_strength": "Надійність пароля користувача",
"global_settings_setting_postfix_compatibility_help": "Компроміс між сумісністю і безпекою для сервера Postfix. Впливає на шифри (і інші аспекти, пов'язані з безпекою)",
"global_settings_setting_ssh_compatibility_help": "Компроміс між сумісністю і безпекою для SSH-сервера. Впливає на шифри (і інші аспекти, пов'язані з безпекою)",
"global_settings_setting_ssh_password_authentication_help": "Дозволити автентифікацію паролем для SSH",
"global_settings_setting_ssh_port": "SSH-порт",
"global_settings_setting_webadmin_allowlist_help": "IP-адреси, яким дозволений доступ до вебадміністрації. Через кому.",
"global_settings_setting_webadmin_allowlist_enabled_help": "Дозволити доступ до вебадміністрації тільки деяким IP-адресам.",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "Дозволити використання (застарілого) ключа DSA для конфігурації демона SSH",
"global_settings_setting_smtp_allow_ipv6_help": "Дозволити використання IPv6 для отримання і надсилання листів е-пошти",
"global_settings_setting_smtp_relay_enabled_help": "Хост SMTP-ретрансляції, який буде використовуватися для надсилання е-пошти замість цього зразка Yunohost. Корисно, якщо ви знаходитеся в одній із цих ситуацій: ваш 25 порт заблокований вашим провайдером або VPS провайдером, у вас є житловий IP в списку DUHL, ви не можете налаштувати зворотний DNS або цей сервер не доступний безпосередньо в Інтернеті і ви хочете використовувати інший сервер для відправки електронних листів.",
"migration_0024_rebuild_python_venv_disclaimer_base": "Після оновлення до Debian Bullseye деякі застосунки Python потрібно частково перебудувати, щоб їх було перетворено на нову версію Python, яка постачається в Debian (з технічної точки зору: те, що називається «virtualenv», потрібно створити заново). Тим часом ці застосунки Python можуть не працювати. YunoHost може спробувати перебудувати virtualenv для деяких із них, як описано нижче. Для інших застосунків або якщо спроба відновлення не вдається, вам потрібно буде вручну примусово оновити їх.", "migration_0024_rebuild_python_venv_disclaimer_base": "Після оновлення до Debian Bullseye деякі застосунки Python потрібно частково перебудувати, щоб їх було перетворено на нову версію Python, яка постачається в Debian (з технічної точки зору: те, що називається «virtualenv», потрібно створити заново). Тим часом ці застосунки Python можуть не працювати. YunoHost може спробувати перебудувати virtualenv для деяких із них, як описано нижче. Для інших застосунків або якщо спроба відновлення не вдається, вам потрібно буде вручну примусово оновити їх.",
"migration_0024_rebuild_python_venv_broken_app": "Пропущено {app}, бо virtualenv не можна легко перебудувати для цього застосунку. Натомість вам слід виправити ситуацію, примусово оновивши застосунок за допомогою `yunohost app upgrade --force {app}`.", "migration_0024_rebuild_python_venv_broken_app": "Пропущено {app}, бо virtualenv не можна легко перебудувати для цього застосунку. Натомість вам слід виправити ситуацію, примусово оновивши застосунок за допомогою `yunohost app upgrade --force {app}`.",
"migration_0024_rebuild_python_venv_disclaimer_rebuild": "Буде зроблена спроба перебудувати virtualenv для таких застосунків (Примітка: операція може зайняти деякий час!): {rebuild_apps}", "migration_0024_rebuild_python_venv_disclaimer_rebuild": "Буде зроблена спроба перебудувати virtualenv для таких застосунків (Примітка: операція може зайняти деякий час!): {rebuild_apps}",

View file

@ -300,21 +300,11 @@
"group_already_exist": "群组{group}已经存在", "group_already_exist": "群组{group}已经存在",
"good_practices_about_admin_password": "现在,您将设置一个新的管理员密码。 密码至少应包含8个字符。并且出于安全考虑建议使用较长的密码同时尽可能使用各种字符大写小写数字和特殊字符。", "good_practices_about_admin_password": "现在,您将设置一个新的管理员密码。 密码至少应包含8个字符。并且出于安全考虑建议使用较长的密码同时尽可能使用各种字符大写小写数字和特殊字符。",
"global_settings_unknown_type": "意外的情况,设置{setting}似乎具有类型 {unknown_type} ,但是系统不支持该类型。", "global_settings_unknown_type": "意外的情况,设置{setting}似乎具有类型 {unknown_type} ,但是系统不支持该类型。",
"global_settings_setting_backup_compress_tar_archives": "创建新备份时,请压缩档案(.tar.gz) ,而不要压缩未压缩的档案(.tar)。注意启用此选项意味着创建较小的备份存档但是初始备份过程将明显更长且占用大量CPU。",
"global_settings_setting_smtp_relay_password": "SMTP中继主机密码", "global_settings_setting_smtp_relay_password": "SMTP中继主机密码",
"global_settings_setting_smtp_relay_user": "SMTP中继用户帐户", "global_settings_setting_smtp_relay_user": "SMTP中继用户帐户",
"global_settings_setting_smtp_relay_port": "SMTP中继端口", "global_settings_setting_smtp_relay_port": "SMTP中继端口",
"global_settings_setting_smtp_allow_ipv6": "允许使用IPv6接收和发送邮件",
"global_settings_setting_ssowat_panel_overlay_enabled": "启用SSOwat面板覆盖", "global_settings_setting_ssowat_panel_overlay_enabled": "启用SSOwat面板覆盖",
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "允许使用DSA主机密钥进行SSH守护程序配置不建议使用",
"global_settings_unknown_setting_from_settings_file": "设置中的未知密钥:'{setting_key}',将其丢弃并保存在/etc/yunohost/settings-unknown.json中", "global_settings_unknown_setting_from_settings_file": "设置中的未知密钥:'{setting_key}',将其丢弃并保存在/etc/yunohost/settings-unknown.json中",
"global_settings_setting_security_ssh_port": "SSH端口",
"global_settings_setting_security_postfix_compatibility": "Postfix服务器的兼容性与安全性的权衡。影响密码以及其他与安全性有关的方面",
"global_settings_setting_security_ssh_compatibility": "SSH服务器的兼容性与安全性的权衡。影响密码以及其他与安全性有关的方面",
"global_settings_setting_security_password_user_strength": "用户密码强度",
"global_settings_setting_security_password_admin_strength": "管理员密码强度",
"global_settings_setting_security_nginx_compatibility": "Web服务器NGINX的兼容性与安全性的权衡影响密码以及其他与安全性有关的方面",
"global_settings_setting_pop3_enabled": "为邮件服务器启用POP3协议",
"global_settings_reset_success": "以前的设置现在已经备份到{path}", "global_settings_reset_success": "以前的设置现在已经备份到{path}",
"global_settings_key_doesnt_exists": "全局设置中不存在键'{settings_key}',您可以通过运行 'yunohost settings list'来查看所有可用键", "global_settings_key_doesnt_exists": "全局设置中不存在键'{settings_key}',您可以通过运行 'yunohost settings list'来查看所有可用键",
"global_settings_cant_write_settings": "无法保存设置文件,原因: {reason}", "global_settings_cant_write_settings": "无法保存设置文件,原因: {reason}",
@ -455,7 +445,6 @@
"regenconf_up_to_date": "类别'{category}'的配置已经是最新的", "regenconf_up_to_date": "类别'{category}'的配置已经是最新的",
"regenconf_file_kept_back": "配置文件'{conf}'预计将被regen-conf类别{category})删除,但被保留了下来。", "regenconf_file_kept_back": "配置文件'{conf}'预计将被regen-conf类别{category})删除,但被保留了下来。",
"good_practices_about_user_password": "现在,您将设置一个新的管理员密码。 密码至少应包含8个字符。并且出于安全考虑建议使用较长的密码同时尽可能使用各种字符大写小写数字和特殊字符", "good_practices_about_user_password": "现在,您将设置一个新的管理员密码。 密码至少应包含8个字符。并且出于安全考虑建议使用较长的密码同时尽可能使用各种字符大写小写数字和特殊字符",
"global_settings_setting_smtp_relay_host": "使用SMTP中继主机来代替这个YunoHost实例发送邮件。如果你有以下情况就很有用:你的25端口被你的ISP或VPS提供商封锁你有一个住宅IP列在DUHL上你不能配置反向DNS或者这个服务器没有直接暴露在互联网上你想使用其他服务器来发送邮件。",
"domain_cannot_remove_main_add_new_one": "你不能删除'{domain}',因为它是主域和你唯一的域,你需要先用'yunohost domain add <another-domain.com>'添加另一个域,然后用'yunohost domain main-domain -n <another-domain.com>'设置为主域,然后你可以用'yunohost domain remove {domain}'删除域", "domain_cannot_remove_main_add_new_one": "你不能删除'{domain}',因为它是主域和你唯一的域,你需要先用'yunohost domain add <another-domain.com>'添加另一个域,然后用'yunohost domain main-domain -n <another-domain.com>'设置为主域,然后你可以用'yunohost domain remove {domain}'删除域",
"domain_cannot_add_xmpp_upload": "你不能添加以'xmpp-upload.'开头的域名。这种名称是为YunoHost中集成的XMPP上传功能保留的。", "domain_cannot_add_xmpp_upload": "你不能添加以'xmpp-upload.'开头的域名。这种名称是为YunoHost中集成的XMPP上传功能保留的。",
"domain_cannot_remove_main": "你不能删除'{domain}',因为它是主域,你首先需要用'yunohost domain main-domain -n <another-domain>'设置另一个域作为主域;这里是候选域的列表: {other_domains}", "domain_cannot_remove_main": "你不能删除'{domain}',因为它是主域,你首先需要用'yunohost domain main-domain -n <another-domain>'设置另一个域作为主域;这里是候选域的列表: {other_domains}",
@ -604,5 +593,15 @@
"diagnosis_apps_allgood": "所有已安装的应用程序都遵守基本的打包原则", "diagnosis_apps_allgood": "所有已安装的应用程序都遵守基本的打包原则",
"diagnosis_apps_deprecated_practices": "此应用程序的安装 版本仍然使用一些超旧的弃用打包原则。推荐您升级它。", "diagnosis_apps_deprecated_practices": "此应用程序的安装 版本仍然使用一些超旧的弃用打包原则。推荐您升级它。",
"diagnosis_apps_issue": "发现应用{ app } 存在问题", "diagnosis_apps_issue": "发现应用{ app } 存在问题",
"diagnosis_description_apps": "应用" "diagnosis_description_apps": "应用",
"global_settings_setting_backup_compress_tar_archives_help": "创建新备份时,请压缩档案(.tar.gz) ,而不要压缩未压缩的档案(.tar)。注意启用此选项意味着创建较小的备份存档但是初始备份过程将明显更长且占用大量CPU。",
"global_settings_setting_nginx_compatibility_help": "Web服务器NGINX的兼容性与安全性的权衡影响密码以及其他与安全性有关的方面",
"global_settings_setting_admin_strength": "管理员密码强度",
"global_settings_setting_user_strength": "用户密码强度",
"global_settings_setting_postfix_compatibility_help": "Postfix服务器的兼容性与安全性的权衡。影响密码以及其他与安全性有关的方面",
"global_settings_setting_ssh_compatibility_help": "SSH服务器的兼容性与安全性的权衡。影响密码以及其他与安全性有关的方面",
"global_settings_setting_ssh_port": "SSH端口",
"global_settings_setting_ssh_allow_deprecated_dsa_hostkey_help": "允许使用DSA主机密钥进行SSH守护程序配置不建议使用",
"global_settings_setting_smtp_allow_ipv6_help": "允许使用IPv6接收和发送邮件",
"global_settings_setting_smtp_relay_enabled_help": "使用SMTP中继主机来代替这个YunoHost实例发送邮件。如果你有以下情况就很有用:你的25端口被你的ISP或VPS提供商封锁你有一个住宅IP列在DUHL上你不能配置反向DNS或者这个服务器没有直接暴露在互联网上你想使用其他服务器来发送邮件。"
} }

16
maintenance/agplv3.tpl Normal file
View file

@ -0,0 +1,16 @@
Copyright (c) ${years} ${owner}
This file is part of ${projectname} (see ${projecturl})
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

View file

@ -99,15 +99,6 @@ def find_expected_string_keys():
for m in ("log_" + match for match in p4.findall(content)): for m in ("log_" + match for match in p4.findall(content)):
yield m yield m
# Global settings descriptions
# Will be on a line like : ("service.ssh.allow_deprecated_dsa_hostkey", {"type": "bool", ...
p5 = re.compile(r" \(\n*\s*[\"\'](\w[\w\.]+)[\"\'],")
content = open(ROOT + "src/settings.py").read()
for m in (
"global_settings_setting_" + s.replace(".", "_") for s in p5.findall(content)
):
yield m
# Keys for the actionmap ... # Keys for the actionmap ...
for category in yaml.safe_load(open(ROOT + "share/actionsmap.yml")).values(): for category in yaml.safe_load(open(ROOT + "share/actionsmap.yml")).values():
if "actions" not in category.keys(): if "actions" not in category.keys():
@ -143,6 +134,7 @@ def find_expected_string_keys():
for key in registrars[registrar].keys(): for key in registrars[registrar].keys():
yield f"domain_config_{key}" yield f"domain_config_{key}"
# Domain config panel
domain_config = toml.load(open(ROOT + "share/config_domain.toml")) domain_config = toml.load(open(ROOT + "share/config_domain.toml"))
for panel in domain_config.values(): for panel in domain_config.values():
if not isinstance(panel, dict): if not isinstance(panel, dict):
@ -155,6 +147,24 @@ def find_expected_string_keys():
continue continue
yield f"domain_config_{key}" yield f"domain_config_{key}"
# Global settings
global_config = toml.load(open(ROOT + "share/config_global.toml"))
# Boring hard-coding because there's no simple other way idk
settings_without_help_key = ["smtp_relay_host", "smtp_relay_password", "smtp_relay_port", "smtp_relay_user", "ssh_port", "ssowat_panel_overlay_enabled", "root_password", "root_access_explain", "root_password_confirm"]
for panel in global_config.values():
if not isinstance(panel, dict):
continue
for section in panel.values():
if not isinstance(section, dict):
continue
for key, values in section.items():
if not isinstance(values, dict):
continue
yield f"global_settings_setting_{key}"
if key not in settings_without_help_key:
yield f"global_settings_setting_{key}_help"
############################################################################### ###############################################################################
# Compare keys used and keys defined # # Compare keys used and keys defined #

View file

@ -0,0 +1,12 @@
# To run this you'll need to:
#
# pip3 install licenseheaders
licenseheaders \
-o "YunoHost Contributors" \
-n "YunoHost" \
-u "https://yunohost.org" \
-t ./agplv3.tpl \
--current-year \
-f ../src/*.py ../src/{utils,diagnosers,authenticators}/*.py

Binary file not shown.

View file

@ -43,7 +43,7 @@ _global:
help: Display YunoHost packages versions help: Display YunoHost packages versions
action: callback action: callback
callback: callback:
method: yunohost.utils.packages.ynh_packages_version method: yunohost.utils.system.ynh_packages_version
return: true return: true
############################# #############################
@ -73,19 +73,28 @@ user:
pattern: &pattern_username pattern: &pattern_username
- !!str ^[a-z0-9_]+$ - !!str ^[a-z0-9_]+$
- "pattern_username" - "pattern_username"
-F:
full: --fullname
help: The full name of the user. For example 'Camille Dupont'
extra:
ask: ask_fullname
required: False
pattern: &pattern_fullname
- !!str ^([^\W\d_]{1,30}[ ,.'-]{0,3})+$
- "pattern_fullname"
-f: -f:
full: --firstname full: --firstname
help: Deprecated. Use --fullname instead.
extra: extra:
ask: ask_firstname required: False
required: True
pattern: &pattern_firstname pattern: &pattern_firstname
- !!str ^([^\W\d_]{1,30}[ ,.'-]{0,3})+$ - !!str ^([^\W\d_]{1,30}[ ,.'-]{0,3})+$
- "pattern_firstname" - "pattern_firstname"
-l: -l:
full: --lastname full: --lastname
help: Deprecated. Use --fullname instead.
extra: extra:
ask: ask_lastname required: False
required: True
pattern: &pattern_lastname pattern: &pattern_lastname
- !!str ^([^\W\d_]{1,30}[ ,.'-]{0,3})+$ - !!str ^([^\W\d_]{1,30}[ ,.'-]{0,3})+$
- "pattern_lastname" - "pattern_lastname"
@ -136,12 +145,19 @@ user:
arguments: arguments:
username: username:
help: Username to update help: Username to update
-F:
full: --fullname
help: The full name of the user. For example 'Camille Dupont'
extra:
pattern: *pattern_fullname
-f: -f:
full: --firstname full: --firstname
help: Deprecated. Use --fullname instead.
extra: extra:
pattern: *pattern_firstname pattern: *pattern_firstname
-l: -l:
full: --lastname full: --lastname
help: Deprecated. Use --fullname instead.
extra: extra:
pattern: *pattern_lastname pattern: *pattern_lastname
-m: -m:
@ -443,6 +459,19 @@ domain:
--exclude-subdomains: --exclude-subdomains:
help: Filter out domains that are obviously subdomains of other declared domains help: Filter out domains that are obviously subdomains of other declared domains
action: store_true action: store_true
--tree:
help: Display domains as a tree
action: store_true
### domain_info()
info:
action_help: Get domain aggredated data
api: GET /domains/<domain>
arguments:
domain:
help: Domain to check
extra:
pattern: *pattern_domain
### domain_add() ### domain_add()
add: add:
@ -552,6 +581,7 @@ domain:
### domain_url_available() ### domain_url_available()
url-available: url-available:
hide_in_help: True
action_help: Check availability of a web path action_help: Check availability of a web path
api: GET /domain/<domain>/urlavailable api: GET /domain/<domain>/urlavailable
arguments: arguments:
@ -562,6 +592,20 @@ domain:
path: path:
help: The path to check (e.g. /coffee) help: The path to check (e.g. /coffee)
### domain_action_run()
action-run:
hide_in_help: True
action_help: Run domain action
api: PUT /domain/<domain>/actions/<action>
arguments:
domain:
help: Domain name
action:
help: action id
-a:
full: --args
help: Serialized arguments for action (i.e. "foo=bar&lorem=ipsum")
subcategories: subcategories:
config: config:
@ -571,7 +615,9 @@ domain:
### domain_config_get() ### domain_config_get()
get: get:
action_help: Display a domain configuration action_help: Display a domain configuration
api: GET /domains/<domain>/config api:
- GET /domains/<domain>/config
- GET /domains/<domain>/config/<key>
arguments: arguments:
domain: domain:
help: Domain name help: Domain name
@ -590,7 +636,7 @@ domain:
### domain_config_set() ### domain_config_set()
set: set:
action_help: Apply a new configuration action_help: Apply a new configuration
api: PUT /domains/<domain>/config api: PUT /domains/<domain>/config/<key>
arguments: arguments:
domain: domain:
help: Domain name help: Domain name
@ -868,6 +914,7 @@ app:
### app_register_url() ### app_register_url()
register-url: register-url:
hide_in_help: True
action_help: Book/register a web path for a given app action_help: Book/register a web path for a given app
arguments: arguments:
app: app:
@ -880,6 +927,7 @@ app:
### app_makedefault() ### app_makedefault()
makedefault: makedefault:
hide_in_help: True
action_help: Redirect domain root to an app action_help: Redirect domain root to an app
api: PUT /apps/<app>/default api: PUT /apps/<app>/default
arguments: arguments:
@ -941,7 +989,9 @@ app:
### app_config_get() ### app_config_get()
get: get:
action_help: Display an app configuration action_help: Display an app configuration
api: GET /apps/<app>/config-panel api:
- GET /apps/<app>/config
- GET /apps/<app>/config/<key>
arguments: arguments:
app: app:
help: App name help: App name
@ -960,7 +1010,7 @@ app:
### app_config_set() ### app_config_set()
set: set:
action_help: Apply a new configuration action_help: Apply a new configuration
api: PUT /apps/<app>/config api: PUT /apps/<app>/config/<key>
arguments: arguments:
app: app:
help: App name help: App name
@ -1065,6 +1115,7 @@ backup:
### backup_download() ### backup_download()
download: download:
hide_in_help: True
action_help: (API only) Request to download the file action_help: (API only) Request to download the file
api: GET /backups/<name>/download api: GET /backups/<name>/download
arguments: arguments:
@ -1093,6 +1144,11 @@ settings:
list: list:
action_help: list all entries of the settings action_help: list all entries of the settings
api: GET /settings api: GET /settings
arguments:
-f:
full: --full
help: Display all details (meant to be used by the API)
action: store_true
### settings_get() ### settings_get()
get: get:
@ -1101,22 +1157,29 @@ settings:
arguments: arguments:
key: key:
help: Settings key help: Settings key
--full: -f:
help: Show more details full: --full
help: Display all details (meant to be used by the API)
action: store_true
-e:
full: --export
help: Only export key/values, meant to be reimported using "config set --args-file"
action: store_true action: store_true
### settings_set() ### settings_set()
set: set:
action_help: set an entry value in the settings action_help: set an entry value in the settings
api: POST /settings/<key> api: PUT /settings/<key>
arguments: arguments:
key: key:
help: Settings key help: The question or form key
nargs: '?'
-v: -v:
full: --value full: --value
help: new value help: new value
extra: -a:
required: True full: --args
help: Serialized arguments for new configuration (i.e. "mail_in=0&mail_out=0")
### settings_reset_all() ### settings_reset_all()
reset-all: reset-all:
@ -1433,10 +1496,10 @@ tools:
category_help: Specific tools category_help: Specific tools
actions: actions:
### tools_adminpw() ### tools_rootpw()
adminpw: rootpw:
action_help: Change password of admin and root users action_help: Change root password
api: PUT /adminpw api: PUT /rootpw
arguments: arguments:
-n: -n:
full: --new-password full: --new-password
@ -1471,6 +1534,20 @@ tools:
ask: ask_main_domain ask: ask_main_domain
pattern: *pattern_domain pattern: *pattern_domain
required: True required: True
-u:
full: --username
help: Username for the first (admin) user. For example 'camille'
extra:
ask: ask_admin_username
pattern: *pattern_username
required: True
-F:
full: --fullname
help: The full name for the first (admin) user. For example 'Camille Dupont'
extra:
ask: ask_admin_fullname
required: True
pattern: *pattern_fullname
-p: -p:
full: --password full: --password
help: YunoHost admin password help: YunoHost admin password
@ -1482,14 +1559,10 @@ tools:
--ignore-dyndns: --ignore-dyndns:
help: Do not subscribe domain to a DynDNS service help: Do not subscribe domain to a DynDNS service
action: store_true action: store_true
--force-password:
help: Use this if you really want to set a weak password
action: store_true
--force-diskspace: --force-diskspace:
help: Use this if you really want to install YunoHost on a setup with less than 10 GB on the root filesystem help: Use this if you really want to install YunoHost on a setup with less than 10 GB on the root filesystem
action: store_true action: store_true
### tools_update() ### tools_update()
update: update:
action_help: YunoHost update action_help: YunoHost update
@ -1651,6 +1724,7 @@ hook:
### hook_info() ### hook_info()
info: info:
hide_in_help: True
action_help: Get information about a given hook action_help: Get information about a given hook
arguments: arguments:
action: action:
@ -1680,6 +1754,7 @@ hook:
### hook_callback() ### hook_callback()
callback: callback:
hide_in_help: True
action_help: Execute all scripts binded to an action action_help: Execute all scripts binded to an action
arguments: arguments:
action: action:
@ -1702,6 +1777,7 @@ hook:
### hook_exec() ### hook_exec()
exec: exec:
hide_in_help: True
action_help: Execute hook from a file with arguments action_help: Execute hook from a file with arguments
arguments: arguments:
path: path:

View file

@ -5,18 +5,19 @@ i18n = "domain_config"
# Other things we may want to implement in the future: # Other things we may want to implement in the future:
# #
# - maindomain handling # - maindomain handling
# - default app
# - autoredirect www in nginx conf # - autoredirect www in nginx conf
# - ? # - ?
# #
[feature] [feature]
name = "Features"
[feature.app] [feature.app]
[feature.app.default_app] [feature.app.default_app]
type = "app" type = "app"
filter = "is_webapp" filter = "is_webapp"
default = "_none" default = "_none"
[feature.mail] [feature.mail]
#services = ['postfix', 'dovecot'] #services = ['postfix', 'dovecot']
@ -28,17 +29,17 @@ i18n = "domain_config"
[feature.mail.mail_out] [feature.mail.mail_out]
type = "boolean" type = "boolean"
default = 1 default = 1
[feature.mail.mail_in] [feature.mail.mail_in]
type = "boolean" type = "boolean"
default = 1 default = 1
#[feature.mail.backup_mx] #[feature.mail.backup_mx]
#type = "tags" #type = "tags"
#default = [] #default = []
#pattern.regexp = '^([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$' #pattern.regexp = '^([^\W_A-Z]+([-]*[^\W_A-Z]+)*\.)+((xn--)?[^\W_]{2,})$'
#pattern.error = "pattern_error" #pattern.error = "pattern_error"
[feature.xmpp] [feature.xmpp]
[feature.xmpp.xmpp] [feature.xmpp.xmpp]
@ -46,11 +47,10 @@ i18n = "domain_config"
default = 0 default = 0
[dns] [dns]
name = "DNS"
[dns.registrar]
optional = true
# This part is automatically generated in DomainConfigPanel [dns.registrar]
# This part is automatically generated in DomainConfigPanel
# [dns.advanced] # [dns.advanced]
# #
@ -58,3 +58,54 @@ i18n = "domain_config"
# type = "number" # type = "number"
# min = 0 # min = 0
# default = 3600 # default = 3600
[cert]
name = "Certificate"
[cert.cert]
[cert.cert.cert_summary]
type = "alert"
# Automatically filled by DomainConfigPanel
[cert.cert.cert_validity]
type = "number"
readonly = true
visible = "false"
# Automatically filled by DomainConfigPanel
[cert.cert.cert_issuer]
type = "string"
visible = false
# Automatically filled by DomainConfigPanel
[cert.cert.acme_eligible]
type = "boolean"
visible = false
# Automatically filled by DomainConfigPanel
[cert.cert.acme_eligible_explain]
type = "alert"
style = "warning"
visible = "acme_eligible == false || acme_elligible == null"
[cert.cert.cert_no_checks]
ask = "Ignore diagnosis checks"
type = "boolean"
default = false
visible = "acme_eligible == false || acme_elligible == null"
[cert.cert.cert_install]
type = "button"
icon = "star"
style = "success"
visible = "issuer != 'letsencrypt'"
enabled = "acme_eligible || cert_no_checks"
[cert.cert.cert_renew]
type = "button"
icon = "refresh"
style = "warning"
visible = "issuer == 'letsencrypt'"
enabled = "acme_eligible || cert_no_checks"

152
share/config_global.toml Normal file
View file

@ -0,0 +1,152 @@
version = "1.0"
i18n = "global_settings_setting"
[security]
name = "Security"
[security.password]
name = "Passwords"
[security.password.admin_strength]
type = "select"
choices.1 = "Require at least 8 chars"
choices.2 = "ditto, but also require at least one digit, one lower and one upper char"
choices.3 = "ditto, but also require at least one special char"
choices.4 = "ditto, but also require at least 12 chars"
default = "1"
[security.password.user_strength]
type = "select"
choices.1 = "Require at least 8 chars"
choices.2 = "ditto, but also require at least one digit, one lower and one upper char"
choices.3 = "ditto, but also require at least one special char"
choices.4 = "ditto, but also require at least 12 chars"
default = "1"
[security.ssh]
name = "SSH"
[security.ssh.ssh_compatibility]
type = "select"
choices.intermediate = "Intermediate (compatible with older softwares)"
choices.modern = "Modern (recommended)"
default = "modern"
[security.ssh.ssh_port]
type = "number"
default = 22
[security.ssh.ssh_password_authentication]
type = "boolean"
default = true
[security.nginx]
name = "NGINX (web server)"
[security.nginx.nginx_redirect_to_https]
type = "boolean"
default = true
[security.nginx.nginx_compatibility]
type = "select"
choices.intermediate = "Intermediate (compatible with Firefox 27, Android 4.4.2, Chrome 31, Edge, IE 11, Opera 20, and Safari 9)"
choices.modern = "Modern (compatible with Firefox 63, Android 10.0, Chrome 70, Edge 75, Opera 57, and Safari 12.1)"
default = "intermediate"
[security.postfix]
name = "Postfix (SMTP email server)"
[security.postfix.postfix_compatibility]
type = "select"
choices.intermediate = "Intermediate (allows TLS 1.2)"
choices.modern = "Modern (TLS 1.3 only)"
default = "intermediate"
[security.webadmin]
name = "Webadmin"
[security.webadmin.webadmin_allowlist_enabled]
type = "boolean"
default = false
[security.webadmin.webadmin_allowlist]
type = "tags"
visible = "webadmin_allowlist_enabled"
optional = true
default = ""
[security.root_access]
name = "Change root password"
[security.root_access.root_access_explain]
type = "alert"
style = "info"
icon = "info"
[security.root_access.root_password]
type = "password"
optional = true
default = ""
[security.root_access.root_password_confirm]
type = "password"
optional = true
default = ""
[security.experimental]
name = "Experimental"
[security.experimental.security_experimental_enabled]
type = "boolean"
default = false
[email]
name = "Email"
[email.pop3]
name = "POP3"
[email.pop3.pop3_enabled]
type = "boolean"
default = false
[email.smtp]
name = "SMTP"
[email.smtp.smtp_allow_ipv6]
type = "boolean"
default = true
[email.smtp.smtp_relay_enabled]
type = "boolean"
default = false
[email.smtp.smtp_relay_host]
type = "string"
default = ""
optional = true
visible="smtp_relay_enabled"
[email.smtp.smtp_relay_port]
type = "number"
default = 587
visible="smtp_relay_enabled"
[email.smtp.smtp_relay_user]
type = "string"
default = ""
optional = true
visible="smtp_relay_enabled"
[email.smtp.smtp_relay_password]
type = "password"
default = ""
optional = true
visible="smtp_relay_enabled"
help = "" # This is empty string on purpose, otherwise the core automatically set the 'good_practice_admin_password' string here which is not relevant, because the admin is not actually "choosing" the password ...
[misc]
name = "Other"
[misc.portal]
name = "User portal"
[misc.portal.ssowat_panel_overlay_enabled]
type = "boolean"
default = true
[misc.backup]
name = "Backup"
[misc.backup.backup_compress_tar_archives]
type = "boolean"
default = false

View file

@ -1,5 +1,22 @@
#! /usr/bin/python #! /usr/bin/python
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import sys import sys

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,21 @@
#
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import re import re
@ -19,7 +37,7 @@ logger = getActionLogger("yunohost.app_catalog")
APPS_CATALOG_CACHE = "/var/cache/yunohost/repo" APPS_CATALOG_CACHE = "/var/cache/yunohost/repo"
APPS_CATALOG_CONF = "/etc/yunohost/apps_catalog.yml" APPS_CATALOG_CONF = "/etc/yunohost/apps_catalog.yml"
APPS_CATALOG_API_VERSION = 2 APPS_CATALOG_API_VERSION = 3
APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default" APPS_CATALOG_DEFAULT_URL = "https://app.yunohost.org/default"
@ -48,8 +66,8 @@ def app_catalog(full=False, with_categories=False):
"level": infos["level"], "level": infos["level"],
} }
else: else:
infos["manifest"]["arguments"] = _set_default_ask_questions( infos["manifest"]["install"] = _set_default_ask_questions(
infos["manifest"].get("arguments", {}) infos["manifest"].get("install", {})
) )
# Trim info for categories if not using --full # Trim info for categories if not using --full
@ -232,6 +250,11 @@ def _load_apps_catalog():
) )
continue continue
if info.get("level") == "?":
info["level"] = -1
# FIXME: we may want to autoconvert all v0/v1 manifest to v2 here
# so that everything is consistent in terms of APIs, datastructure format etc
info["repository"] = apps_catalog_id info["repository"] = apps_catalog_id
merged_catalog["apps"][app] = info merged_catalog["apps"][app] = info

View file

@ -1,5 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import logging import logging
import ldap import ldap
@ -11,37 +27,64 @@ from moulinette.authentication import BaseAuthenticator
from moulinette.utils.text import random_ascii from moulinette.utils.text import random_ascii
from yunohost.utils.error import YunohostError, YunohostAuthenticationError from yunohost.utils.error import YunohostError, YunohostAuthenticationError
from yunohost.utils.ldap import _get_ldap_interface
logger = logging.getLogger("yunohost.authenticators.ldap_admin")
session_secret = random_ascii() session_secret = random_ascii()
logger = logging.getLogger("yunohost.authenticators.ldap_admin")
LDAP_URI = "ldap://localhost:389"
ADMIN_GROUP = "cn=admins,ou=groups"
AUTH_DN = "uid={uid},ou=users,dc=yunohost,dc=org"
class Authenticator(BaseAuthenticator): class Authenticator(BaseAuthenticator):
name = "ldap_admin" name = "ldap_admin"
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.uri = "ldap://localhost:389" pass
self.basedn = "dc=yunohost,dc=org"
self.admindn = "cn=admin,dc=yunohost,dc=org"
def _authenticate_credentials(self, credentials=None): def _authenticate_credentials(self, credentials=None):
# TODO : change authentication format try:
# to support another dn to support multi-admins admins = _get_ldap_interface().search(ADMIN_GROUP, attrs=["memberUid"])[0].get("memberUid", [])
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
# Force-reset existing LDAP interface
from yunohost.utils import ldap as ldaputils
ldaputils._ldap_interface = None
try:
admins = _get_ldap_interface().search(ADMIN_GROUP, attrs=["memberUid"])[0].get("memberUid", [])
except ldap.SERVER_DOWN:
raise YunohostError("ldap_server_down")
try:
uid, password = credentials.split(":", 1)
except ValueError:
raise YunohostError("invalid_credentials")
# Here we're explicitly using set() which are handled as hash tables
# and should prevent timing attacks to find out the admin usernames?
if uid not in set(admins):
raise YunohostError("invalid_credentials")
dn = AUTH_DN.format(uid=uid)
def _reconnect(): def _reconnect():
con = ldap.ldapobject.ReconnectLDAPObject( con = ldap.ldapobject.ReconnectLDAPObject(
self.uri, retry_max=10, retry_delay=0.5 LDAP_URI, retry_max=10, retry_delay=0.5
) )
con.simple_bind_s(self.admindn, credentials) con.simple_bind_s(dn, password)
return con return con
try: try:
con = _reconnect() con = _reconnect()
except ldap.INVALID_CREDENTIALS: except ldap.INVALID_CREDENTIALS:
raise YunohostError("invalid_password") raise YunohostError("invalid_credentials")
except ldap.SERVER_DOWN: except ldap.SERVER_DOWN:
# ldap is down, attempt to restart it before really failing # ldap is down, attempt to restart it before really failing
logger.warning(m18n.n("ldap_server_is_down_restart_it")) logger.warning(m18n.n("ldap_server_is_down_restart_it"))
@ -61,11 +104,8 @@ class Authenticator(BaseAuthenticator):
logger.warning("Error during ldap authentication process: %s", e) logger.warning("Error during ldap authentication process: %s", e)
raise raise
else: else:
if who != self.admindn: if who != dn:
raise YunohostError( raise YunohostError(f"Not logged with the appropriate identity ? Found {who}, expected {dn} !?", raw_msg=True)
f"Not logged with the appropriate identity ? Found {who}, expected {self.admindn} !?",
raw_msg=True,
)
finally: finally:
# Free the connection, we don't really need it to keep it open as the point is only to check authentication... # Free the connection, we don't really need it to keep it open as the point is only to check authentication...
if con: if con:

View file

@ -1,28 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2013 YunoHost #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
""" yunohost_backup.py
Manage backups
"""
import os import os
import re import re
import json import json
@ -39,9 +32,8 @@ from functools import reduce
from packaging import version from packaging import version
from moulinette import Moulinette, m18n from moulinette import Moulinette, m18n
from moulinette.utils import filesystem
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml, rm, chown, chmod
from moulinette.utils.process import check_output from moulinette.utils.process import check_output
import yunohost.domain import yunohost.domain
@ -50,6 +42,7 @@ from yunohost.app import (
_is_installed, _is_installed,
_make_environment_for_app_script, _make_environment_for_app_script,
_make_tmp_workdir_for_app, _make_tmp_workdir_for_app,
_get_manifest_of_app,
) )
from yunohost.hook import ( from yunohost.hook import (
hook_list, hook_list,
@ -67,8 +60,12 @@ from yunohost.tools import (
from yunohost.regenconf import regen_conf from yunohost.regenconf import regen_conf
from yunohost.log import OperationLogger, is_unit_operation from yunohost.log import OperationLogger, is_unit_operation
from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.error import YunohostError, YunohostValidationError
from yunohost.utils.packages import ynh_packages_version from yunohost.utils.system import (
from yunohost.utils.filesystem import free_space_in_directory free_space_in_directory,
get_ynh_package_version,
binary_to_human,
space_used_by_directory,
)
from yunohost.settings import settings_get from yunohost.settings import settings_get
BACKUP_PATH = "/home/yunohost.backup" BACKUP_PATH = "/home/yunohost.backup"
@ -312,7 +309,7 @@ class BackupManager:
"size_details": self.size_details, "size_details": self.size_details,
"apps": self.apps_return, "apps": self.apps_return,
"system": self.system_return, "system": self.system_return,
"from_yunohost_version": ynh_packages_version()["yunohost"]["version"], "from_yunohost_version": get_ynh_package_version("yunohost")["version"],
} }
@property @property
@ -342,7 +339,7 @@ class BackupManager:
# FIXME replace isdir by exists ? manage better the case where the path # FIXME replace isdir by exists ? manage better the case where the path
# exists # exists
if not os.path.isdir(self.work_dir): if not os.path.isdir(self.work_dir):
filesystem.mkdir(self.work_dir, 0o750, parents=True, uid="admin") mkdir(self.work_dir, 0o750, parents=True)
elif self.is_tmp_work_dir: elif self.is_tmp_work_dir:
logger.debug( logger.debug(
@ -357,8 +354,8 @@ class BackupManager:
# If umount succeeded, remove the directory (we checked that # If umount succeeded, remove the directory (we checked that
# we're in /home/yunohost.backup/tmp so that should be okay... # we're in /home/yunohost.backup/tmp so that should be okay...
# c.f. method clean() which also does this) # c.f. method clean() which also does this)
filesystem.rm(self.work_dir, recursive=True, force=True) rm(self.work_dir, recursive=True, force=True)
filesystem.mkdir(self.work_dir, 0o750, parents=True, uid="admin") mkdir(self.work_dir, 0o750, parents=True)
# #
# Backup target management # # Backup target management #
@ -535,7 +532,7 @@ class BackupManager:
successfull_system = self.targets.list("system", include=["Success", "Warning"]) successfull_system = self.targets.list("system", include=["Success", "Warning"])
if not successfull_apps and not successfull_system: if not successfull_apps and not successfull_system:
filesystem.rm(self.work_dir, True, True) rm(self.work_dir, True, True)
raise YunohostError("backup_nothings_done") raise YunohostError("backup_nothings_done")
# Add unlisted files from backup tmp dir # Add unlisted files from backup tmp dir
@ -577,7 +574,7 @@ class BackupManager:
env_var["YNH_BACKUP_CSV"] = tmp_csv env_var["YNH_BACKUP_CSV"] = tmp_csv
if app is not None: if app is not None:
env_var.update(_make_environment_for_app_script(app)) env_var.update(_make_environment_for_app_script(app, action="backup"))
env_var["YNH_APP_BACKUP_DIR"] = os.path.join( env_var["YNH_APP_BACKUP_DIR"] = os.path.join(
self.work_dir, "apps", app, "backup" self.work_dir, "apps", app, "backup"
) )
@ -647,7 +644,7 @@ class BackupManager:
restore_hooks_dir = os.path.join(self.work_dir, "hooks", "restore") restore_hooks_dir = os.path.join(self.work_dir, "hooks", "restore")
if not os.path.exists(restore_hooks_dir): if not os.path.exists(restore_hooks_dir):
filesystem.mkdir(restore_hooks_dir, mode=0o700, parents=True, uid="root") mkdir(restore_hooks_dir, mode=0o700, parents=True, uid="root")
restore_hooks = hook_list("restore")["hooks"] restore_hooks = hook_list("restore")["hooks"]
@ -714,7 +711,7 @@ class BackupManager:
tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app) tmp_workdir_for_app = _make_tmp_workdir_for_app(app=app)
try: try:
# Prepare backup directory for the app # Prepare backup directory for the app
filesystem.mkdir(tmp_app_bkp_dir, 0o700, True, uid="root") mkdir(tmp_app_bkp_dir, 0o700, True, uid="root")
# Copy the app settings to be able to call _common.sh # Copy the app settings to be able to call _common.sh
shutil.copytree(app_setting_path, settings_dir) shutil.copytree(app_setting_path, settings_dir)
@ -753,7 +750,7 @@ class BackupManager:
# Remove tmp files in all situations # Remove tmp files in all situations
finally: finally:
shutil.rmtree(tmp_workdir_for_app) shutil.rmtree(tmp_workdir_for_app)
filesystem.rm(env_dict["YNH_BACKUP_CSV"], force=True) rm(env_dict["YNH_BACKUP_CSV"], force=True)
# #
# Actual backup archive creation / method management # # Actual backup archive creation / method management #
@ -796,7 +793,7 @@ class BackupManager:
if row["dest"] == "info.json": if row["dest"] == "info.json":
continue continue
size = disk_usage(row["source"]) size = space_used_by_directory(row["source"], follow_symlinks=False)
# Add size to apps details # Add size to apps details
splitted_dest = row["dest"].split("/") splitted_dest = row["dest"].split("/")
@ -949,7 +946,7 @@ class RestoreManager:
ret = subprocess.call(["umount", self.work_dir]) ret = subprocess.call(["umount", self.work_dir])
if ret != 0: if ret != 0:
logger.warning(m18n.n("restore_cleaning_failed")) logger.warning(m18n.n("restore_cleaning_failed"))
filesystem.rm(self.work_dir, recursive=True, force=True) rm(self.work_dir, recursive=True, force=True)
# #
# Restore target manangement # # Restore target manangement #
@ -979,7 +976,7 @@ class RestoreManager:
available_restore_system_hooks = hook_list("restore")["hooks"] available_restore_system_hooks = hook_list("restore")["hooks"]
custom_restore_hook_folder = os.path.join(CUSTOM_HOOK_FOLDER, "restore") custom_restore_hook_folder = os.path.join(CUSTOM_HOOK_FOLDER, "restore")
filesystem.mkdir(custom_restore_hook_folder, 755, parents=True, force=True) mkdir(custom_restore_hook_folder, 755, parents=True, force=True)
for system_part in target_list: for system_part in target_list:
# By default, we'll use the restore hooks on the current install # By default, we'll use the restore hooks on the current install
@ -1084,7 +1081,7 @@ class RestoreManager:
else: else:
raise YunohostError("restore_removing_tmp_dir_failed") raise YunohostError("restore_removing_tmp_dir_failed")
filesystem.mkdir(self.work_dir, parents=True) mkdir(self.work_dir, parents=True)
self.method.mount() self.method.mount()
@ -1402,7 +1399,7 @@ class RestoreManager:
# Delete _common.sh file in backup # Delete _common.sh file in backup
common_file = os.path.join(app_backup_in_archive, "_common.sh") common_file = os.path.join(app_backup_in_archive, "_common.sh")
filesystem.rm(common_file, force=True) rm(common_file, force=True)
# Check if the app has a restore script # Check if the app has a restore script
app_restore_script_in_archive = os.path.join(app_scripts_in_archive, "restore") app_restore_script_in_archive = os.path.join(app_scripts_in_archive, "restore")
@ -1418,14 +1415,14 @@ class RestoreManager:
) )
app_scripts_new_path = os.path.join(app_settings_new_path, "scripts") app_scripts_new_path = os.path.join(app_settings_new_path, "scripts")
shutil.copytree(app_settings_in_archive, app_settings_new_path) shutil.copytree(app_settings_in_archive, app_settings_new_path)
filesystem.chmod(app_settings_new_path, 0o400, 0o400, True) chmod(app_settings_new_path, 0o400, 0o400, True)
filesystem.chown(app_scripts_new_path, "root", None, True) chown(app_scripts_new_path, "root", None, True)
# Copy the app scripts to a writable temporary folder # Copy the app scripts to a writable temporary folder
tmp_workdir_for_app = _make_tmp_workdir_for_app() tmp_workdir_for_app = _make_tmp_workdir_for_app()
copytree(app_scripts_in_archive, tmp_workdir_for_app) copytree(app_scripts_in_archive, tmp_workdir_for_app)
filesystem.chmod(tmp_workdir_for_app, 0o700, 0o700, True) chmod(tmp_workdir_for_app, 0o700, 0o700, True)
filesystem.chown(tmp_workdir_for_app, "root", None, True) chown(tmp_workdir_for_app, "root", None, True)
restore_script = os.path.join(tmp_workdir_for_app, "restore") restore_script = os.path.join(tmp_workdir_for_app, "restore")
# Restore permissions # Restore permissions
@ -1494,7 +1491,7 @@ class RestoreManager:
# FIXME : workdir should be a tmp workdir # FIXME : workdir should be a tmp workdir
app_workdir = os.path.join(self.work_dir, "apps", app_instance_name, "settings") app_workdir = os.path.join(self.work_dir, "apps", app_instance_name, "settings")
env_dict = _make_environment_for_app_script( env_dict = _make_environment_for_app_script(
app_instance_name, workdir=app_workdir app_instance_name, workdir=app_workdir, action="restore"
) )
env_dict.update( env_dict.update(
{ {
@ -1509,6 +1506,15 @@ class RestoreManager:
operation_logger.extra["env"] = env_dict operation_logger.extra["env"] = env_dict
operation_logger.flush() operation_logger.flush()
manifest = _get_manifest_of_app(app_settings_in_archive)
if manifest["packaging_format"] >= 2:
from yunohost.utils.resources import AppResourceManager
try:
AppResourceManager(app_instance_name, wanted=manifest, current={}).apply(rollback_if_failure=True)
except Exception:
# FIXME : improve error handling ....
raise
# Execute the app install script # Execute the app install script
restore_failed = True restore_failed = True
try: try:
@ -1727,7 +1733,7 @@ class BackupMethod:
raise YunohostError("backup_cleaning_failed") raise YunohostError("backup_cleaning_failed")
if self.manager.is_tmp_work_dir: if self.manager.is_tmp_work_dir:
filesystem.rm(self.work_dir, True, True) rm(self.work_dir, True, True)
def _check_is_enough_free_space(self): def _check_is_enough_free_space(self):
""" """
@ -1775,11 +1781,11 @@ class BackupMethod:
# Be sure the parent dir of destination exists # Be sure the parent dir of destination exists
if not os.path.isdir(dest_dir): if not os.path.isdir(dest_dir):
filesystem.mkdir(dest_dir, parents=True) mkdir(dest_dir, parents=True)
# For directory, attempt to mount bind # For directory, attempt to mount bind
if os.path.isdir(src): if os.path.isdir(src):
filesystem.mkdir(dest, parents=True, force=True) mkdir(dest, parents=True, force=True)
try: try:
subprocess.check_call(["mount", "--rbind", src, dest]) subprocess.check_call(["mount", "--rbind", src, dest])
@ -1832,7 +1838,7 @@ class BackupMethod:
# to mounting error # to mounting error
# Compute size to copy # Compute size to copy
size = sum(disk_usage(path["source"]) for path in paths_needed_to_be_copied) size = sum(space_used_by_directory(path["source"], follow_symlinks=False) for path in paths_needed_to_be_copied)
size /= 1024 * 1024 # Convert bytes to megabytes size /= 1024 * 1024 # Convert bytes to megabytes
# Ask confirmation for copying # Ask confirmation for copying
@ -1884,7 +1890,7 @@ class CopyBackupMethod(BackupMethod):
dest_parent = os.path.dirname(dest) dest_parent = os.path.dirname(dest)
if not os.path.exists(dest_parent): if not os.path.exists(dest_parent):
filesystem.mkdir(dest_parent, 0o700, True, uid="admin") mkdir(dest_parent, 0o700, True)
if os.path.isdir(source): if os.path.isdir(source):
shutil.copytree(source, dest) shutil.copytree(source, dest)
@ -1902,7 +1908,7 @@ class CopyBackupMethod(BackupMethod):
if not os.path.isdir(self.repo): if not os.path.isdir(self.repo):
raise YunohostError("backup_no_uncompress_archive_dir") raise YunohostError("backup_no_uncompress_archive_dir")
filesystem.mkdir(self.work_dir, parent=True) mkdir(self.work_dir, parent=True)
ret = subprocess.call(["mount", "-r", "--rbind", self.repo, self.work_dir]) ret = subprocess.call(["mount", "-r", "--rbind", self.repo, self.work_dir])
if ret == 0: if ret == 0:
return return
@ -1928,7 +1934,7 @@ class TarBackupMethod(BackupMethod):
def _archive_file(self): def _archive_file(self):
if isinstance(self.manager, BackupManager) and settings_get( if isinstance(self.manager, BackupManager) and settings_get(
"backup.compress_tar_archives" "misc.backup.backup_compress_tar_archives"
): ):
return os.path.join(self.repo, self.name + ".tar.gz") return os.path.join(self.repo, self.name + ".tar.gz")
@ -1946,7 +1952,7 @@ class TarBackupMethod(BackupMethod):
""" """
if not os.path.exists(self.repo): if not os.path.exists(self.repo):
filesystem.mkdir(self.repo, 0o750, parents=True, uid="admin") mkdir(self.repo, 0o750, parents=True)
# Check free space in output # Check free space in output
self._check_is_enough_free_space() self._check_is_enough_free_space()
@ -2628,9 +2634,9 @@ def _create_archive_dir():
if os.path.lexists(ARCHIVES_PATH): if os.path.lexists(ARCHIVES_PATH):
raise YunohostError("backup_output_symlink_dir_broken", path=ARCHIVES_PATH) raise YunohostError("backup_output_symlink_dir_broken", path=ARCHIVES_PATH)
# Create the archive folder, with 'admin' as owner, such that # Create the archive folder, with 'admins' as groupowner, such that
# people can scp archives out of the server # people can scp archives out of the server
mkdir(ARCHIVES_PATH, mode=0o750, parents=True, uid="admin", gid="root") mkdir(ARCHIVES_PATH, mode=0o770, parents=True, gid="admins")
def _call_for_each_path(self, callback, csv_path=None): def _call_for_each_path(self, callback, csv_path=None):
@ -2667,31 +2673,3 @@ def _recursive_umount(directory):
continue continue
return everything_went_fine return everything_went_fine
def disk_usage(path):
# We don't do this in python with os.stat because we don't want
# to follow symlinks
du_output = check_output(["du", "-sb", path], shell=False)
return int(du_output.split()[0])
def binary_to_human(n, customary=False):
"""
Convert bytes or bits into human readable format with binary prefix
Keyword argument:
n -- Number to convert
customary -- Use customary symbol instead of IEC standard
"""
symbols = ("Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi")
if customary:
symbols = ("K", "M", "G", "T", "P", "E", "Z", "Y")
prefix = {}
for i, s in enumerate(symbols):
prefix[s] = 1 << (i + 1) * 10
for s in reversed(symbols):
if n >= prefix[s]:
value = float(n) / prefix[s]
return "{:.1f}{}".format(value, s)
return "%s" % n

View file

@ -1,32 +1,24 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2016 YUNOHOST.ORG #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
yunohost_certificate.py
Manage certificates, in particular Let's encrypt
"""
import os import os
import sys import sys
import shutil import shutil
import pwd
import grp
import subprocess import subprocess
import glob import glob
@ -34,7 +26,8 @@ from datetime import datetime
from moulinette import m18n from moulinette import m18n
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_file from moulinette.utils.filesystem import read_file, chown, chmod
from moulinette.utils.process import check_output
from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate
from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.error import YunohostError, YunohostValidationError
@ -95,15 +88,17 @@ def certificate_status(domains, full=False):
if not full: if not full:
del status["subject"] del status["subject"]
del status["CA_name"] del status["CA_name"]
status["CA_type"] = status["CA_type"]["verbose"]
status["summary"] = status["summary"]["verbose"]
if full: if full:
try: try:
_check_domain_is_ready_for_ACME(domain) _check_domain_is_ready_for_ACME(domain)
status["ACME_eligible"] = True status["ACME_eligible"] = True
except Exception: except Exception as e:
status["ACME_eligible"] = False if e.key == 'certmanager_domain_not_diagnosed_yet':
status["ACME_eligible"] = None # = unknown status
else:
status["ACME_eligible"] = False
del status["domain"] del status["domain"]
certificates[domain] = status certificates[domain] = status
@ -131,6 +126,7 @@ def certificate_install(domain_list, force=False, no_checks=False, self_signed=F
def _certificate_install_selfsigned(domain_list, force=False): def _certificate_install_selfsigned(domain_list, force=False):
failed_cert_install = []
for domain in domain_list: for domain in domain_list:
operation_logger = OperationLogger( operation_logger = OperationLogger(
@ -154,7 +150,7 @@ def _certificate_install_selfsigned(domain_list, force=False):
if not force and os.path.isfile(current_cert_file): if not force and os.path.isfile(current_cert_file):
status = _get_status(domain) status = _get_status(domain)
if status["summary"]["code"] in ("good", "great"): if status["style"] == "success":
raise YunohostValidationError( raise YunohostValidationError(
"certmanager_attempt_to_replace_valid_cert", domain=domain "certmanager_attempt_to_replace_valid_cert", domain=domain
) )
@ -216,7 +212,7 @@ def _certificate_install_selfsigned(domain_list, force=False):
if ( if (
status status
and status["CA_type"]["code"] == "self-signed" and status["CA_type"] == "selfsigned"
and status["validity"] > 3648 and status["validity"] > 3648
): ):
logger.success( logger.success(
@ -225,9 +221,17 @@ def _certificate_install_selfsigned(domain_list, force=False):
operation_logger.success() operation_logger.success()
else: else:
msg = f"Installation of self-signed certificate installation for {domain} failed !" msg = f"Installation of self-signed certificate installation for {domain} failed !"
failed_cert_install.append(domain)
logger.error(msg) logger.error(msg)
logger.error(status)
operation_logger.error(msg) operation_logger.error(msg)
if failed_cert_install:
raise YunohostError(
"certmanager_cert_install_failed_selfsigned",
domains=",".join(failed_cert_install)
)
def _certificate_install_letsencrypt(domains, force=False, no_checks=False): def _certificate_install_letsencrypt(domains, force=False, no_checks=False):
from yunohost.domain import domain_list, _assert_domain_exists from yunohost.domain import domain_list, _assert_domain_exists
@ -241,7 +245,7 @@ def _certificate_install_letsencrypt(domains, force=False, no_checks=False):
for domain in domain_list()["domains"]: for domain in domain_list()["domains"]:
status = _get_status(domain) status = _get_status(domain)
if status["CA_type"]["code"] != "self-signed": if status["CA_type"] != "selfsigned":
continue continue
domains.append(domain) domains.append(domain)
@ -253,12 +257,13 @@ def _certificate_install_letsencrypt(domains, force=False, no_checks=False):
# Is it self-signed? # Is it self-signed?
status = _get_status(domain) status = _get_status(domain)
if not force and status["CA_type"]["code"] != "self-signed": if not force and status["CA_type"] != "selfsigned":
raise YunohostValidationError( raise YunohostValidationError(
"certmanager_domain_cert_not_selfsigned", domain=domain "certmanager_domain_cert_not_selfsigned", domain=domain
) )
# Actual install steps # Actual install steps
failed_cert_install = []
for domain in domains: for domain in domains:
if not no_checks: if not no_checks:
@ -287,11 +292,18 @@ def _certificate_install_letsencrypt(domains, force=False, no_checks=False):
logger.error( logger.error(
f"Please consider checking the 'DNS records' (basic) and 'Web' categories of the diagnosis to check for possible issues that may prevent installing a Let's Encrypt certificate on domain {domain}." f"Please consider checking the 'DNS records' (basic) and 'Web' categories of the diagnosis to check for possible issues that may prevent installing a Let's Encrypt certificate on domain {domain}."
) )
failed_cert_install.append(domain)
else: else:
logger.success(m18n.n("certmanager_cert_install_success", domain=domain)) logger.success(m18n.n("certmanager_cert_install_success", domain=domain))
operation_logger.success() operation_logger.success()
if failed_cert_install:
raise YunohostError(
"certmanager_cert_install_failed",
domains=",".join(failed_cert_install)
)
def certificate_renew(domains, force=False, no_checks=False, email=False): def certificate_renew(domains, force=False, no_checks=False, email=False):
""" """
@ -314,7 +326,7 @@ def certificate_renew(domains, force=False, no_checks=False, email=False):
# Does it have a Let's Encrypt cert? # Does it have a Let's Encrypt cert?
status = _get_status(domain) status = _get_status(domain)
if status["CA_type"]["code"] != "lets-encrypt": if status["CA_type"] != "letsencrypt":
continue continue
# Does it expire soon? # Does it expire soon?
@ -349,7 +361,7 @@ def certificate_renew(domains, force=False, no_checks=False, email=False):
) )
# Does it have a Let's Encrypt cert? # Does it have a Let's Encrypt cert?
if status["CA_type"]["code"] != "lets-encrypt": if status["CA_type"] != "letsencrypt":
raise YunohostValidationError( raise YunohostValidationError(
"certmanager_attempt_to_renew_nonLE_cert", domain=domain "certmanager_attempt_to_renew_nonLE_cert", domain=domain
) )
@ -361,6 +373,7 @@ def certificate_renew(domains, force=False, no_checks=False, email=False):
) )
# Actual renew steps # Actual renew steps
failed_cert_install = []
for domain in domains: for domain in domains:
if not no_checks: if not no_checks:
@ -402,6 +415,8 @@ def certificate_renew(domains, force=False, no_checks=False, email=False):
logger.error(stack.getvalue()) logger.error(stack.getvalue())
logger.error(str(e)) logger.error(str(e))
failed_cert_install.append(domain)
if email: if email:
logger.error("Sending email with details to root ...") logger.error("Sending email with details to root ...")
_email_renewing_failed(domain, msg + "\n" + str(e), stack.getvalue()) _email_renewing_failed(domain, msg + "\n" + str(e), stack.getvalue())
@ -409,6 +424,11 @@ def certificate_renew(domains, force=False, no_checks=False, email=False):
logger.success(m18n.n("certmanager_cert_renew_success", domain=domain)) logger.success(m18n.n("certmanager_cert_renew_success", domain=domain))
operation_logger.success() operation_logger.success()
if failed_cert_install:
raise YunohostError(
"certmanager_cert_renew_failed",
domains=",".join(failed_cert_install)
)
# #
# Back-end stuff # # Back-end stuff #
@ -537,9 +557,9 @@ def _fetch_and_enable_new_certificate(domain, no_checks=False):
_enable_certificate(domain, new_cert_folder) _enable_certificate(domain, new_cert_folder)
# Check the status of the certificate is now good # Check the status of the certificate is now good
status_summary = _get_status(domain)["summary"] status_style = _get_status(domain)["style"]
if status_summary["code"] != "great": if status_style != "success":
raise YunohostError( raise YunohostError(
"certmanager_certificate_fetching_or_enabling_failed", domain=domain "certmanager_certificate_fetching_or_enabling_failed", domain=domain
) )
@ -633,59 +653,42 @@ def _get_status(domain):
) )
days_remaining = (valid_up_to - datetime.utcnow()).days days_remaining = (valid_up_to - datetime.utcnow()).days
if cert_issuer in ["yunohost.org"] + yunohost.domain.domain_list()["domains"]: self_signed_issuers = ["yunohost.org"] + yunohost.domain.domain_list()["domains"]
CA_type = {
"code": "self-signed",
"verbose": "Self-signed",
}
# FIXME: is the .ca.cnf one actually used anywhere ? x_x
conf = os.path.join(SSL_DIR, "openssl.ca.cnf")
if os.path.exists(conf):
self_signed_issuers.append(check_output(f"grep commonName_default {conf}").split()[-1])
conf = os.path.join(SSL_DIR, "openssl.cnf")
if os.path.exists(conf):
self_signed_issuers.append(check_output(f"grep commonName_default {conf}").split()[-1])
if cert_issuer in self_signed_issuers:
CA_type = "selfsigned"
elif organization_name == "Let's Encrypt": elif organization_name == "Let's Encrypt":
CA_type = { CA_type = "letsencrypt"
"code": "lets-encrypt",
"verbose": "Let's Encrypt",
}
else: else:
CA_type = { CA_type = "other"
"code": "other-unknown",
"verbose": "Other / Unknown",
}
if days_remaining <= 0: if days_remaining <= 0:
status_summary = { style = "danger"
"code": "critical", summary = "expired"
"verbose": "CRITICAL", elif CA_type == "selfsigned":
} style = "warning"
summary = "selfsigned"
elif CA_type["code"] in ("self-signed", "fake-lets-encrypt"):
status_summary = {
"code": "warning",
"verbose": "WARNING",
}
elif days_remaining < VALIDITY_LIMIT: elif days_remaining < VALIDITY_LIMIT:
status_summary = { style = "warning"
"code": "attention", summary = "abouttoexpire"
"verbose": "About to expire", elif CA_type == "other":
} style = "success"
summary = "ok"
elif CA_type["code"] == "other-unknown": elif CA_type == "letsencrypt":
status_summary = { style = "success"
"code": "good", summary = "letsencrypt"
"verbose": "Good",
}
elif CA_type["code"] == "lets-encrypt":
status_summary = {
"code": "great",
"verbose": "Great!",
}
else: else:
status_summary = { # shouldnt happen, because CA_type can be only selfsigned, letsencrypt, or other
"code": "unknown", style = ""
"verbose": "Unknown?", summary = "wat"
}
return { return {
"domain": domain, "domain": domain,
@ -693,7 +696,8 @@ def _get_status(domain):
"CA_name": cert_issuer, "CA_name": cert_issuer,
"CA_type": CA_type, "CA_type": CA_type,
"validity": days_remaining, "validity": days_remaining,
"summary": status_summary, "style": style,
"summary": summary,
} }
@ -719,11 +723,8 @@ def _generate_key(destination_path):
def _set_permissions(path, user, group, permissions): def _set_permissions(path, user, group, permissions):
uid = pwd.getpwnam(user).pw_uid chown(path, user, group)
gid = grp.getgrnam(group).gr_gid chmod(path, permissions)
os.chown(path, uid, gid)
os.chmod(path, permissions)
def _enable_certificate(domain, new_cert_folder): def _enable_certificate(domain, new_cert_folder):
@ -791,7 +792,7 @@ def _check_domain_is_ready_for_ACME(domain):
or {} or {}
) )
parent_domain = _get_parent_domain_of(domain) parent_domain = _get_parent_domain_of(domain, return_self=True)
dnsrecords = ( dnsrecords = (
Diagnoser.get_cached_report( Diagnoser.get_cached_report(
@ -908,6 +909,4 @@ def _name_self_CA():
def _tail(n, file_path): def _tail(n, file_path):
from moulinette.utils.process import check_output
return check_output(f"tail -n {n} '{file_path}'") return check_output(f"tail -n {n} '{file_path}'")

View file

@ -1,5 +1,21 @@
#!/usr/bin/env python #
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import json import json
import subprocess import subprocess
@ -9,7 +25,11 @@ from moulinette.utils import log
from moulinette.utils.process import check_output from moulinette.utils.process import check_output
from moulinette.utils.filesystem import read_file, read_json, write_to_json from moulinette.utils.filesystem import read_file, read_json, write_to_json
from yunohost.diagnosis import Diagnoser from yunohost.diagnosis import Diagnoser
from yunohost.utils.packages import ynh_packages_version from yunohost.utils.system import (
ynh_packages_version,
system_virt,
system_arch,
)
logger = log.getActionLogger("yunohost.diagnosis") logger = log.getActionLogger("yunohost.diagnosis")
@ -22,15 +42,12 @@ class MyDiagnoser(Diagnoser):
def run(self): def run(self):
# Detect virt technology (if not bare metal) and arch virt = system_virt()
# Gotta have this "|| true" because it systemd-detect-virt return 'none'
# with an error code on bare metal ~.~
virt = check_output("systemd-detect-virt || true", shell=True)
if virt.lower() == "none": if virt.lower() == "none":
virt = "bare-metal" virt = "bare-metal"
# Detect arch # Detect arch
arch = check_output("dpkg --print-architecture") arch = system_arch()
hardware = dict( hardware = dict(
meta={"test": "hardware"}, meta={"test": "hardware"},
status="INFO", status="INFO",

View file

@ -1,5 +1,21 @@
#!/usr/bin/env python #
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import re import re
import os import os
import random import random

View file

@ -1,5 +1,21 @@
#!/usr/bin/env python #
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import re import re
from typing import List from typing import List

View file

@ -1,5 +1,21 @@
#!/usr/bin/env python #
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
from typing import List from typing import List

View file

@ -1,11 +1,27 @@
#!/usr/bin/env python #
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import random import random
import requests import requests
from typing import List from typing import List
from moulinette.utils.filesystem import read_file from moulinette.utils.filesystem import read_file, mkdir, rm
from yunohost.diagnosis import Diagnoser from yunohost.diagnosis import Diagnoser
from yunohost.domain import domain_list from yunohost.domain import domain_list
@ -46,8 +62,8 @@ class MyDiagnoser(Diagnoser):
domains_to_check.append(domain) domains_to_check.append(domain)
self.nonce = "".join(random.choice("0123456789abcedf") for i in range(16)) self.nonce = "".join(random.choice("0123456789abcedf") for i in range(16))
os.system("rm -rf /tmp/.well-known/ynh-diagnosis/") rm("/tmp/.well-known/ynh-diagnosis/", recursive=True, force=True)
os.system("mkdir -p /tmp/.well-known/ynh-diagnosis/") mkdir("/tmp/.well-known/ynh-diagnosis/", parents=True)
os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % self.nonce) os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % self.nonce)
if not domains_to_check: if not domains_to_check:

View file

@ -1,5 +1,21 @@
#!/usr/bin/env python #
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import dns.resolver import dns.resolver
import re import re
@ -291,7 +307,7 @@ class MyDiagnoser(Diagnoser):
if global_ipv4: if global_ipv4:
outgoing_ips.append(global_ipv4) outgoing_ips.append(global_ipv4)
if settings_get("smtp.allow_ipv6"): if settings_get("email.smtp.smtp_allow_ipv6"):
ipv6 = Diagnoser.get_cached_report("ip", {"test": "ipv6"}) or {} ipv6 = Diagnoser.get_cached_report("ip", {"test": "ipv6"}) or {}
if ipv6.get("status") == "SUCCESS": if ipv6.get("status") == "SUCCESS":
outgoing_ipversions.append(6) outgoing_ipversions.append(6)

View file

@ -1,5 +1,21 @@
#!/usr/bin/env python #
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
from typing import List from typing import List

View file

@ -1,4 +1,21 @@
#!/usr/bin/env python #
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import psutil import psutil
import datetime import datetime

View file

@ -1,5 +1,21 @@
#!/usr/bin/env python #
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import re import re
from typing import List from typing import List
@ -53,7 +69,7 @@ class MyDiagnoser(Diagnoser):
) )
# Check consistency between actual ssh port in sshd_config vs. setting # Check consistency between actual ssh port in sshd_config vs. setting
ssh_port_setting = settings_get("security.ssh.port") ssh_port_setting = settings_get("security.ssh.ssh_port")
ssh_port_line = re.findall( ssh_port_line = re.findall(
r"\bPort *([0-9]{2,5})\b", read_file("/etc/ssh/sshd_config") r"\bPort *([0-9]{2,5})\b", read_file("/etc/ssh/sshd_config")
) )

View file

@ -1,5 +1,21 @@
#!/usr/bin/env python #
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
from typing import List from typing import List

View file

@ -0,0 +1,18 @@
#
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

View file

@ -1,29 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2018 YunoHost #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
""" diagnosis.py
Look for possible issues on the server
"""
import re import re
import os import os
import time import time

View file

@ -1,28 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2013 YunoHost #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
""" yunohost_domain.py
Manage domains
"""
import os import os
import re import re
import time import time
@ -35,12 +28,12 @@ from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_file, write_to_file, read_toml, mkdir from moulinette.utils.filesystem import read_file, write_to_file, read_toml, mkdir
from yunohost.domain import ( from yunohost.domain import (
domain_list,
_assert_domain_exists, _assert_domain_exists,
domain_config_get, domain_config_get,
_get_domain_settings, _get_domain_settings,
_set_domain_settings, _set_domain_settings,
_list_subdomains_of, _list_subdomains_of,
_get_parent_domain_of,
) )
from yunohost.utils.dns import dig, is_yunohost_dyndns_domain, is_special_use_tld from yunohost.utils.dns import dig, is_yunohost_dyndns_domain, is_special_use_tld
from yunohost.utils.error import YunohostValidationError, YunohostError from yunohost.utils.error import YunohostValidationError, YunohostError
@ -446,8 +439,8 @@ def _get_dns_zone_for_domain(domain):
# This is another strick to try to prevent this function from being # This is another strick to try to prevent this function from being
# a bottleneck on system with 1 main domain + 10ish subdomains # a bottleneck on system with 1 main domain + 10ish subdomains
# when building the dns conf for the main domain (which will call domain_config_get, etc...) # when building the dns conf for the main domain (which will call domain_config_get, etc...)
parent_domain = domain.split(".", 1)[1] parent_domain = _get_parent_domain_of(domain)
if parent_domain in domain_list()["domains"]: if parent_domain:
parent_cache_file = f"{cache_folder}/{parent_domain}" parent_cache_file = f"{cache_folder}/{parent_domain}"
if ( if (
os.path.exists(parent_cache_file) os.path.exists(parent_cache_file)
@ -512,17 +505,19 @@ def _get_registrar_config_section(domain):
from lexicon.providers.auto import _relevant_provider_for_domain from lexicon.providers.auto import _relevant_provider_for_domain
registrar_infos = {} registrar_infos = {
"name": m18n.n('registrar_infos'), # This is meant to name the config panel section, for proper display in the webadmin
}
dns_zone = _get_dns_zone_for_domain(domain) dns_zone = _get_dns_zone_for_domain(domain)
# If parent domain exists in yunohost # If parent domain exists in yunohost
parent_domain = domain.split(".", 1)[1] parent_domain = _get_parent_domain_of(domain, topest=True)
if parent_domain in domain_list()["domains"]: if parent_domain:
# Dirty hack to have a link on the webadmin # Dirty hack to have a link on the webadmin
if Moulinette.interface.type == "api": if Moulinette.interface.type == "api":
parent_domain_link = f"[{parent_domain}](#/domains/{parent_domain}/config)" parent_domain_link = f"[{parent_domain}](#/domains/{parent_domain}/dns)"
else: else:
parent_domain_link = parent_domain parent_domain_link = parent_domain
@ -532,7 +527,7 @@ def _get_registrar_config_section(domain):
"style": "info", "style": "info",
"ask": m18n.n( "ask": m18n.n(
"domain_dns_registrar_managed_in_parent_domain", "domain_dns_registrar_managed_in_parent_domain",
parent_domain=domain, parent_domain=parent_domain,
parent_domain_link=parent_domain_link, parent_domain_link=parent_domain_link,
), ),
"value": "parent_domain", "value": "parent_domain",
@ -647,7 +642,7 @@ def domain_dns_push(operation_logger, domain, dry_run=False, force=False, purge=
return {} return {}
if registrar == "parent_domain": if registrar == "parent_domain":
parent_domain = domain.split(".", 1)[1] parent_domain = _get_parent_domain_of(domain, topest=True)
registar, registrar_credentials = _get_registar_settings(parent_domain) registar, registrar_credentials = _get_registar_settings(parent_domain)
if any(registrar_credentials.values()): if any(registrar_credentials.values()):
raise YunohostValidationError( raise YunohostValidationError(

View file

@ -1,30 +1,25 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2013 YunoHost #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
""" yunohost_domain.py
Manage domains
"""
import os import os
from typing import Dict, Any import time
from typing import List, Optional
from collections import OrderedDict
from moulinette import m18n, Moulinette from moulinette import m18n, Moulinette
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
@ -44,62 +39,134 @@ from yunohost.log import is_unit_operation
logger = getActionLogger("yunohost.domain") logger = getActionLogger("yunohost.domain")
DOMAIN_CONFIG_PATH = "/usr/share/yunohost/config_domain.toml"
DOMAIN_SETTINGS_DIR = "/etc/yunohost/domains" DOMAIN_SETTINGS_DIR = "/etc/yunohost/domains"
# Lazy dev caching to avoid re-query ldap every time we need the domain list # Lazy dev caching to avoid re-query ldap every time we need the domain list
domain_list_cache: Dict[str, Any] = {} # The cache automatically expire every 15 seconds, to prevent desync between
# yunohost CLI and API which run in different processes
domain_list_cache: List[str] = []
domain_list_cache_timestamp = 0
main_domain_cache: Optional[str] = None
main_domain_cache_timestamp = 0
DOMAIN_CACHE_DURATION = 15
def domain_list(exclude_subdomains=False): def _get_maindomain():
global main_domain_cache
global main_domain_cache_timestamp
if not main_domain_cache or abs(main_domain_cache_timestamp - time.time()) > DOMAIN_CACHE_DURATION:
with open("/etc/yunohost/current_host", "r") as f:
main_domain_cache = f.readline().rstrip()
main_domain_cache_timestamp = time.time()
return main_domain_cache
def _get_domains(exclude_subdomains=False):
global domain_list_cache
global domain_list_cache_timestamp
if not domain_list_cache or abs(domain_list_cache_timestamp - time.time()) > DOMAIN_CACHE_DURATION:
from yunohost.utils.ldap import _get_ldap_interface
ldap = _get_ldap_interface()
result = [
entry["virtualdomain"][0]
for entry in ldap.search("ou=domains", "virtualdomain=*", ["virtualdomain"])
]
def cmp_domain(domain):
# Keep the main part of the domain and the extension together
# eg: this.is.an.example.com -> ['example.com', 'an', 'is', 'this']
domain = domain.split(".")
domain[-1] = domain[-2] + domain.pop()
return list(reversed(domain))
domain_list_cache = sorted(result, key=cmp_domain)
domain_list_cache_timestamp = time.time()
if exclude_subdomains:
return [
domain
for domain in domain_list_cache
if not _get_parent_domain_of(domain)
]
return domain_list_cache
def domain_list(exclude_subdomains=False, tree=False):
""" """
List domains List domains
Keyword argument: Keyword argument:
exclude_subdomains -- Filter out domains that are subdomains of other declared domains exclude_subdomains -- Filter out domains that are subdomains of other declared domains
tree -- Display domains as a hierarchy tree
""" """
global domain_list_cache
if not exclude_subdomains and domain_list_cache:
return domain_list_cache
from yunohost.utils.ldap import _get_ldap_interface domains = _get_domains(exclude_subdomains)
main = _get_maindomain()
ldap = _get_ldap_interface() if not tree:
result = [ return {"domains": domains, "main": main}
entry["virtualdomain"][0]
for entry in ldap.search("ou=domains", "virtualdomain=*", ["virtualdomain"])
]
result_list = [] if tree and exclude_subdomains:
for domain in result: return {
if exclude_subdomains: "domains": OrderedDict({domain: {} for domain in domains}),
parent_domain = domain.split(".", 1)[1] "main": main,
if parent_domain in result: }
continue
result_list.append(domain) def get_parent_dict(tree, child):
# If parent exists it should be the last added (see `_get_domains` ordering)
possible_parent = next(reversed(tree)) if tree else None
if possible_parent and child.endswith(f".{possible_parent}"):
return get_parent_dict(tree[possible_parent], child)
return tree
def cmp_domain(domain): result = OrderedDict()
# Keep the main part of the domain and the extension together for domain in domains:
# eg: this.is.an.example.com -> ['example.com', 'an', 'is', 'this'] parent = get_parent_dict(result, domain)
domain = domain.split(".") parent[domain] = OrderedDict()
domain[-1] = domain[-2] + domain.pop()
domain = list(reversed(domain))
return domain
result_list = sorted(result_list, key=cmp_domain) return {"domains": result, "main": main}
# Don't cache answer if using exclude_subdomains
if exclude_subdomains:
return {"domains": result_list, "main": _get_maindomain()}
domain_list_cache = {"domains": result_list, "main": _get_maindomain()} def domain_info(domain):
return domain_list_cache """
Print aggregate data for a specific domain
Keyword argument:
domain -- Domain to be checked
"""
from yunohost.app import app_info
from yunohost.dns import _get_registar_settings
_assert_domain_exists(domain)
registrar, _ = _get_registar_settings(domain)
certificate = domain_cert_status([domain], full=True)["certificates"][domain]
apps = []
for app in _installed_apps():
settings = _get_app_settings(app)
if settings.get("domain") == domain:
apps.append(
{"name": app_info(app)["name"], "id": app, "path": settings["path"]}
)
return {
"certificate": certificate,
"registrar": registrar,
"apps": apps,
"main": _get_maindomain() == domain,
"topest_parent": _get_parent_domain_of(domain, topest=True),
# TODO : add parent / child domains ?
}
def _assert_domain_exists(domain): def _assert_domain_exists(domain):
if domain not in domain_list()["domains"]: if domain not in _get_domains():
raise YunohostValidationError("domain_unknown", domain=domain) raise YunohostValidationError("domain_unknown", domain=domain)
@ -108,26 +175,24 @@ def _list_subdomains_of(parent_domain):
_assert_domain_exists(parent_domain) _assert_domain_exists(parent_domain)
out = [] out = []
for domain in domain_list()["domains"]: for domain in _get_domains():
if domain.endswith(f".{parent_domain}"): if domain.endswith(f".{parent_domain}"):
out.append(domain) out.append(domain)
return out return out
def _get_parent_domain_of(domain): def _get_parent_domain_of(domain, return_self=False, topest=False):
_assert_domain_exists(domain) domains = _get_domains(exclude_subdomains=topest)
if "." not in domain: domain_ = domain
return domain while "." in domain_:
domain_ = domain_.split(".", 1)[1]
if domain_ in domains:
return domain_
parent_domain = domain.split(".", 1)[-1] return domain if return_self else None
if parent_domain not in domain_list()["domains"]:
return domain # Domain is its own parent
else:
return _get_parent_domain_of(parent_domain)
@is_unit_operation() @is_unit_operation()
@ -199,7 +264,7 @@ def domain_add(operation_logger, domain, dyndns=False):
raise YunohostError("domain_creation_failed", domain=domain, error=e) raise YunohostError("domain_creation_failed", domain=domain, error=e)
finally: finally:
global domain_list_cache global domain_list_cache
domain_list_cache = {} domain_list_cache = []
# Don't regen these conf if we're still in postinstall # Don't regen these conf if we're still in postinstall
if os.path.exists("/etc/yunohost/installed"): if os.path.exists("/etc/yunohost/installed"):
@ -256,7 +321,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False):
# Check domain is not the main domain # Check domain is not the main domain
if domain == _get_maindomain(): if domain == _get_maindomain():
other_domains = domain_list()["domains"] other_domains = _get_domains()
other_domains.remove(domain) other_domains.remove(domain)
if other_domains: if other_domains:
@ -317,7 +382,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False):
raise YunohostError("domain_deletion_failed", domain=domain, error=e) raise YunohostError("domain_deletion_failed", domain=domain, error=e)
finally: finally:
global domain_list_cache global domain_list_cache
domain_list_cache = {} domain_list_cache = []
stuff_to_delete = [ stuff_to_delete = [
f"/etc/yunohost/certs/{domain}", f"/etc/yunohost/certs/{domain}",
@ -381,8 +446,8 @@ def domain_main_domain(operation_logger, new_main_domain=None):
# Apply changes to ssl certs # Apply changes to ssl certs
try: try:
write_to_file("/etc/yunohost/current_host", new_main_domain) write_to_file("/etc/yunohost/current_host", new_main_domain)
global domain_list_cache global main_domain_cache
domain_list_cache = {} main_domain_cache = new_main_domain
_set_hostname(new_main_domain) _set_hostname(new_main_domain)
except Exception as e: except Exception as e:
logger.warning(str(e), exc_info=1) logger.warning(str(e), exc_info=1)
@ -410,12 +475,6 @@ def domain_url_available(domain, path):
return len(_get_conflicting_apps(domain, path)) == 0 return len(_get_conflicting_apps(domain, path)) == 0
def _get_maindomain():
with open("/etc/yunohost/current_host", "r") as f:
maindomain = f.readline().rstrip()
return maindomain
def domain_config_get(domain, key="", full=False, export=False): def domain_config_get(domain, key="", full=False, export=False):
""" """
Display a domain configuration Display a domain configuration
@ -499,6 +558,27 @@ class DomainConfigPanel(ConfigPanel):
self.registar_id = toml["dns"]["registrar"]["registrar"]["value"] self.registar_id = toml["dns"]["registrar"]["registrar"]["value"]
del toml["dns"]["registrar"]["registrar"]["value"] del toml["dns"]["registrar"]["registrar"]["value"]
# Cert stuff
if not filter_key or filter_key[0] == "cert":
from yunohost.certificate import certificate_status
status = certificate_status([self.entity], full=True)["certificates"][self.entity]
toml["cert"]["cert"]["cert_summary"]["style"] = status["style"]
# i18n: domain_config_cert_summary_expired
# i18n: domain_config_cert_summary_selfsigned
# i18n: domain_config_cert_summary_abouttoexpire
# i18n: domain_config_cert_summary_ok
# i18n: domain_config_cert_summary_letsencrypt
toml["cert"]["cert"]["cert_summary"]["ask"] = m18n.n(f"domain_config_cert_summary_{status['summary']}")
# Other specific strings used in config panels
# i18n: domain_config_cert_renew_help
# FIXME: Ugly hack to save the cert status and reinject it in _load_current_values ...
self.cert_status = status
return toml return toml
def _load_current_values(self): def _load_current_values(self):
@ -511,6 +591,28 @@ class DomainConfigPanel(ConfigPanel):
if not filter_key or filter_key[0] == "dns": if not filter_key or filter_key[0] == "dns":
self.values["registrar"] = self.registar_id self.values["registrar"] = self.registar_id
# FIXME: Ugly hack to save the cert status and reinject it in _load_current_values ...
if not filter_key or filter_key[0] == "cert":
self.values["cert_validity"] = self.cert_status["validity"]
self.values["cert_issuer"] = self.cert_status["CA_type"]
self.values["acme_eligible"] = self.cert_status["ACME_eligible"]
self.values["summary"] = self.cert_status["summary"]
def domain_action_run(domain, action, args=None):
import urllib.parse
if action == "cert.cert.cert_install":
from yunohost.certificate import certificate_install as action_func
elif action == "cert.cert.cert_renew":
from yunohost.certificate import certificate_renew as action_func
args = dict(urllib.parse.parse_qsl(args or "", keep_blank_values=True))
no_checks = args["cert_no_checks"] in ("y", "yes", "on", "1")
action_func([domain], force=True, no_checks=no_checks)
def _get_domain_settings(domain: str) -> dict: def _get_domain_settings(domain: str) -> dict:

View file

@ -1,28 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2013 YunoHost #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
""" yunohost_dyndns.py
Subscribe and Update DynDNS Hosts
"""
import os import os
import re import re
import json import json

View file

@ -1,28 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2013 YunoHost #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
""" yunohost_firewall.py
Manage firewall rules
"""
import os import os
import yaml import yaml
import miniupnpc import miniupnpc

View file

@ -1,28 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2013 YunoHost #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
""" yunohost_hook.py
Manage hooks
"""
import os import os
import re import re
import sys import sys

View file

@ -1,29 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2018 YunoHost #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
""" yunohost_log.py
Manage debug logs
"""
import os import os
import re import re
import yaml import yaml
@ -38,7 +30,7 @@ from io import IOBase
from moulinette import m18n, Moulinette from moulinette import m18n, Moulinette
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.error import YunohostError, YunohostValidationError
from yunohost.utils.packages import get_ynh_package_version from yunohost.utils.system import get_ynh_package_version
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_file, read_yaml from moulinette.utils.filesystem import read_file, read_yaml

View file

@ -15,8 +15,8 @@ from yunohost.tools import (
) )
from yunohost.app import unstable_apps from yunohost.app import unstable_apps
from yunohost.regenconf import manually_modified_files, _force_clear_hashes from yunohost.regenconf import manually_modified_files, _force_clear_hashes
from yunohost.utils.filesystem import free_space_in_directory from yunohost.utils.system import (
from yunohost.utils.packages import ( free_space_in_directory,
get_ynh_package_version, get_ynh_package_version,
_list_upgradable_apt_packages, _list_upgradable_apt_packages,
) )

View file

@ -7,7 +7,7 @@ from yunohost.utils.error import YunohostError, YunohostValidationError
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from yunohost.tools import Migration from yunohost.tools import Migration
from yunohost.utils.filesystem import free_space_in_directory, space_used_by_directory from yunohost.utils.system import free_space_in_directory, space_used_by_directory
logger = getActionLogger("yunohost.migration") logger = getActionLogger("yunohost.migration")

View file

@ -0,0 +1,41 @@
import os
from yunohost.utils.error import YunohostError
from moulinette.utils.log import getActionLogger
from moulinette.utils.filesystem import read_json, write_to_yaml
from yunohost.tools import Migration
from yunohost.utils.legacy import translate_legacy_settings_to_configpanel_settings
logger = getActionLogger("yunohost.migration")
SETTINGS_PATH = "/etc/yunohost/settings.yml"
OLD_SETTINGS_PATH = "/etc/yunohost/settings.json"
class MyMigration(Migration):
"Migrate old global settings to the new ConfigPanel global settings"
dependencies = ["migrate_to_bullseye"]
def run(self):
if not os.path.exists(OLD_SETTINGS_PATH):
return
try:
old_settings = read_json(OLD_SETTINGS_PATH)
except Exception as e:
raise YunohostError(f"Can't open setting file : {e}", raw_msg=True)
settings = {
translate_legacy_settings_to_configpanel_settings(k).split('.')[-1]: v["value"]
for k, v in old_settings.items()
}
if settings.get("smtp_relay_host"):
settings["smtp_relay_enabled"] = True
# Here we don't use settings_set() from settings.py to prevent
# Questions to be asked when one run the migration from CLI.
write_to_yaml(SETTINGS_PATH, settings)

View file

@ -0,0 +1,111 @@
from moulinette.utils.log import getActionLogger
from yunohost.tools import Migration
logger = getActionLogger("yunohost.migration")
###################################################
# Tools used also for restoration
###################################################
class MyMigration(Migration):
"""
Add new permissions around SSH/SFTP features
"""
introduced_in_version = "11.1" # FIXME?
dependencies = []
ldap_migration_started = False
@Migration.ldap_migration
def run(self, *args):
from yunohost.user import user_list, user_info, user_group_update, user_update
from yunohost.utils.ldap import _get_ldap_interface
from yunohost.permission import permission_sync_to_user
ldap = _get_ldap_interface()
all_users = user_list()["users"].keys()
new_admin_user = None
for user in all_users:
if any(alias.startswith("root@") for alias in user_info(user).get("mail-aliases", [])):
new_admin_user = user
break
self.ldap_migration_started = True
if new_admin_user:
aliases = user_info(new_admin_user).get("mail-aliases", [])
old_admin_aliases_to_remove = [alias for alias in aliases if any(alias.startswith(a) for a in ["root@", "admin@", "admins@", "webmaster@", "postmaster@", "abuse@"])]
user_update(new_admin_user, remove_mailalias=old_admin_aliases_to_remove)
admin_hashs = ldap.search("cn=admin", attrs={"userPassword"})[0]["userPassword"]
stuff_to_delete = [
"cn=admin,ou=sudo",
"cn=admin",
"cn=admins,ou=groups",
]
for stuff in stuff_to_delete:
if ldap.search(stuff):
ldap.remove(stuff)
ldap.add(
"cn=admins,ou=sudo",
{
"cn": ["admins"],
"objectClass": ["top", "sudoRole"],
"sudoCommand": ["ALL"],
"sudoUser": ["%admins"],
"sudoHost": ["ALL"],
}
)
ldap.add(
"cn=admins,ou=groups",
{
"cn": ["admins"],
"objectClass": ["top", "posixGroup", "groupOfNamesYnh", "mailGroup"],
"gidNumber": ["4001"],
"mail": ["root", "admin", "admins", "webmaster", "postmaster", "abuse"],
}
)
permission_sync_to_user()
if new_admin_user:
user_group_update(groupname="admins", add=new_admin_user, sync_perm=True)
# Re-add admin as a regular user
attr_dict = {
"objectClass": [
"mailAccount",
"inetOrgPerson",
"posixAccount",
"userPermissionYnh",
],
"givenName": ["Admin"],
"sn": ["Admin"],
"displayName": ["Admin"],
"cn": ["Admin"],
"uid": ["admin"],
"mail": "admin_legacy",
"maildrop": ["admin"],
"mailuserquota": ["0"],
"userPassword": admin_hashs,
"gidNumber": ["1007"],
"uidNumber": ["1007"],
"homeDirectory": ["/home/admin"],
"loginShell": ["/bin/bash"],
}
ldap.add("uid=admin,ou=users", attr_dict)
user_group_update(groupname="admins", add="admin", sync_perm=True)
def run_after_system_restore(self):
self.run()

View file

@ -1,29 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2014 YUNOHOST.ORG #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
""" yunohost_permission.py
Manage permissions
"""
import re import re
import copy import copy
import grp import grp

View file

@ -1,24 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2019 YunoHost #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
import os import os
import yaml import yaml
import shutil import shutil

View file

@ -1,29 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2013 YunoHost #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
""" yunohost_service.py
Manage services
"""
import re import re
import os import os
import time import time

View file

@ -1,129 +1,39 @@
#
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import json
import subprocess import subprocess
from datetime import datetime
from collections import OrderedDict
from moulinette import m18n from moulinette import m18n
from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.error import YunohostError, YunohostValidationError
from yunohost.utils.config import ConfigPanel, Question
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from yunohost.regenconf import regen_conf from yunohost.regenconf import regen_conf
from yunohost.firewall import firewall_reload from yunohost.firewall import firewall_reload
from yunohost.log import is_unit_operation
from yunohost.utils.legacy import translate_legacy_settings_to_configpanel_settings
logger = getActionLogger("yunohost.settings") logger = getActionLogger("yunohost.settings")
SETTINGS_PATH = "/etc/yunohost/settings.json" SETTINGS_PATH = "/etc/yunohost/settings.yml"
SETTINGS_PATH_OTHER_LOCATION = "/etc/yunohost/settings-%s.json"
def is_boolean(value): def settings_get(key="", full=False, export=False):
TRUE = ["true", "on", "yes", "y", "1"]
FALSE = ["false", "off", "no", "n", "0"]
"""
Ensure a string value is intended as a boolean
Keyword arguments:
arg -- The string to check
Returns:
(is_boolean, boolean_value)
"""
if isinstance(value, bool):
return True, value
if value in [0, 1]:
return True, bool(value)
elif isinstance(value, str):
if str(value).lower() in TRUE + FALSE:
return True, str(value).lower() in TRUE
else:
return False, None
else:
return False, None
# a settings entry is in the form of:
# namespace.subnamespace.name: {type, value, default, description, [choices]}
# choices is only for enum
# the keyname can have as many subnamespace as needed but should have at least
# one level of namespace
# description is implied from the translated strings
# the key is "global_settings_setting_%s" % key.replace(".", "_")
# type can be:
# * bool
# * int
# * string
# * enum (in the form of a python list)
DEFAULTS = OrderedDict(
[
# Password Validation
# -1 disabled, 0 alert if listed, 1 8-letter, 2 normal, 3 strong, 4 strongest
("security.password.admin.strength", {"type": "int", "default": 1}),
("security.password.user.strength", {"type": "int", "default": 1}),
(
"service.ssh.allow_deprecated_dsa_hostkey",
{"type": "bool", "default": False},
),
(
"security.ssh.compatibility",
{
"type": "enum",
"default": "modern",
"choices": ["intermediate", "modern"],
},
),
(
"security.ssh.port",
{"type": "int", "default": 22},
),
(
"security.ssh.password_authentication",
{"type": "bool", "default": True},
),
(
"security.nginx.redirect_to_https",
{
"type": "bool",
"default": True,
},
),
(
"security.nginx.compatibility",
{
"type": "enum",
"default": "intermediate",
"choices": ["intermediate", "modern"],
},
),
(
"security.postfix.compatibility",
{
"type": "enum",
"default": "intermediate",
"choices": ["intermediate", "modern"],
},
),
("pop3.enabled", {"type": "bool", "default": False}),
("smtp.allow_ipv6", {"type": "bool", "default": True}),
("smtp.relay.host", {"type": "string", "default": ""}),
("smtp.relay.port", {"type": "int", "default": 587}),
("smtp.relay.user", {"type": "string", "default": ""}),
("smtp.relay.password", {"type": "string", "default": ""}),
("backup.compress_tar_archives", {"type": "bool", "default": False}),
("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}),
]
)
def settings_get(key, full=False):
""" """
Get an entry value in the settings Get an entry value in the settings
@ -131,28 +41,40 @@ def settings_get(key, full=False):
key -- Settings key key -- Settings key
""" """
settings = _get_settings() if full and export:
if key not in settings:
raise YunohostValidationError( raise YunohostValidationError(
"global_settings_key_doesnt_exists", settings_key=key "You can't use --full and --export together.", raw_msg=True
) )
if full: if full:
return settings[key] mode = "full"
elif export:
mode = "export"
else:
mode = "classic"
return settings[key]["value"] if mode == "classic" and key == "":
raise YunohostValidationError("Missing key", raw_msg=True)
settings = SettingsConfigPanel()
key = translate_legacy_settings_to_configpanel_settings(key)
return settings.get(key, mode)
def settings_list(): def settings_list(full=False, export=True):
""" """
List all entries of the settings List all entries of the settings
""" """
return _get_settings()
if full:
export = False
return settings_get(full=full, export=export)
def settings_set(key, value): @is_unit_operation()
def settings_set(operation_logger, key=None, value=None, args=None, args_file=None):
""" """
Set an entry value in the settings Set an entry value in the settings
@ -161,78 +83,14 @@ def settings_set(key, value):
value -- New value value -- New value
""" """
settings = _get_settings() Question.operation_logger = operation_logger
settings = SettingsConfigPanel()
if key not in settings: key = translate_legacy_settings_to_configpanel_settings(key)
raise YunohostValidationError( return settings.set(key, value, args, args_file, operation_logger=operation_logger)
"global_settings_key_doesnt_exists", settings_key=key
)
key_type = settings[key]["type"]
if key_type == "bool":
boolean_value = is_boolean(value)
if boolean_value[0]:
value = boolean_value[1]
else:
raise YunohostValidationError(
"global_settings_bad_type_for_setting",
setting=key,
received_type=type(value).__name__,
expected_type=key_type,
)
elif key_type == "int":
if not isinstance(value, int) or isinstance(value, bool):
if isinstance(value, str):
try:
value = int(value)
except Exception:
raise YunohostValidationError(
"global_settings_bad_type_for_setting",
setting=key,
received_type=type(value).__name__,
expected_type=key_type,
)
else:
raise YunohostValidationError(
"global_settings_bad_type_for_setting",
setting=key,
received_type=type(value).__name__,
expected_type=key_type,
)
elif key_type == "string":
if not isinstance(value, str):
raise YunohostValidationError(
"global_settings_bad_type_for_setting",
setting=key,
received_type=type(value).__name__,
expected_type=key_type,
)
elif key_type == "enum":
if value not in settings[key]["choices"]:
raise YunohostValidationError(
"global_settings_bad_choice_for_enum",
setting=key,
choice=str(value),
available_choices=", ".join(settings[key]["choices"]),
)
else:
raise YunohostValidationError(
"global_settings_unknown_type", setting=key, unknown_type=key_type
)
old_value = settings[key].get("value")
settings[key]["value"] = value
_save_settings(settings)
try:
trigger_post_change_hook(key, old_value, value)
except Exception as e:
logger.error(f"Post-change hook for setting {key} failed : {e}")
raise
def settings_reset(key): @is_unit_operation()
def settings_reset(operation_logger, key):
""" """
Set an entry value to its default one Set an entry value to its default one
@ -240,18 +98,14 @@ def settings_reset(key):
key -- Settings key key -- Settings key
""" """
settings = _get_settings()
if key not in settings: settings = SettingsConfigPanel()
raise YunohostValidationError( key = translate_legacy_settings_to_configpanel_settings(key)
"global_settings_key_doesnt_exists", settings_key=key return settings.reset(key, operation_logger=operation_logger)
)
settings[key]["value"] = settings[key]["default"]
_save_settings(settings)
def settings_reset_all(): @is_unit_operation()
def settings_reset_all(operation_logger):
""" """
Reset all settings to their default value Reset all settings to their default value
@ -259,110 +113,120 @@ def settings_reset_all():
yes -- Yes I'm sure I want to do that yes -- Yes I'm sure I want to do that
""" """
settings = _get_settings() settings = SettingsConfigPanel()
return settings.reset(operation_logger=operation_logger)
# For now on, we backup the previous settings in case of but we don't have
# any mecanism to take advantage of those backups. It could be a nice
# addition but we'll see if this is a common need.
# Another solution would be to use etckeeper and integrate those
# modification inside of it and take advantage of its git history
old_settings_backup_path = (
SETTINGS_PATH_OTHER_LOCATION % datetime.utcnow().strftime("%F_%X")
)
_save_settings(settings, location=old_settings_backup_path)
for value in settings.values():
value["value"] = value["default"]
_save_settings(settings)
return {
"old_settings_backup_path": old_settings_backup_path,
"message": m18n.n(
"global_settings_reset_success", path=old_settings_backup_path
),
}
def _get_setting_description(key): class SettingsConfigPanel(ConfigPanel):
return m18n.n(f"global_settings_setting_{key}".replace(".", "_")) entity_type = "global"
save_path_tpl = SETTINGS_PATH
save_mode = "diff"
def __init__(self, config_path=None, save_path=None, creation=False):
super().__init__("settings")
def _get_settings(): def _apply(self):
settings = {} root_password = self.new_values.pop("root_password", None)
root_password_confirm = self.new_values.pop("root_password_confirm", None)
for key, value in DEFAULTS.copy().items(): if "root_password" in self.values:
settings[key] = value del self.values["root_password"]
settings[key]["value"] = value["default"] if "root_password_confirm" in self.values:
settings[key]["description"] = _get_setting_description(key) del self.values["root_password_confirm"]
if "root_password" in self.new_values:
del self.new_values["root_password"]
if "root_password_confirm" in self.new_values:
del self.new_values["root_password_confirm"]
if not os.path.exists(SETTINGS_PATH): assert "root_password" not in self.future_values
return settings
# we have a very strict policy on only allowing settings that we know in if root_password and root_password.strip():
# the OrderedDict DEFAULTS
# For various reason, while reading the local settings we might encounter
# settings that aren't in DEFAULTS, those can come from settings key that
# we have removed, errors or the user trying to modify
# /etc/yunohost/settings.json
# To avoid to simply overwrite them, we store them in
# /etc/yunohost/settings-unknown.json in case of
unknown_settings = {}
unknown_settings_path = SETTINGS_PATH_OTHER_LOCATION % "unknown"
if os.path.exists(unknown_settings_path): if root_password != root_password_confirm:
try: raise YunohostValidationError("password_confirmation_not_the_same")
unknown_settings = json.load(open(unknown_settings_path, "r"))
except Exception as e:
logger.warning(f"Error while loading unknown settings {e}")
try: from yunohost.tools import tools_rootpw
with open(SETTINGS_PATH) as settings_fd: tools_rootpw(root_password, check_strength=True)
local_settings = json.load(settings_fd)
for key, value in local_settings.items(): super()._apply()
if key in settings:
settings[key] = value settings = {
settings[key]["description"] = _get_setting_description(key) k: v for k, v in self.future_values.items() if self.values.get(k) != v
else: }
logger.warning( for setting_name, value in settings.items():
m18n.n( try:
"global_settings_unknown_setting_from_settings_file", trigger_post_change_hook(
setting_key=key, setting_name, self.values.get(setting_name), value
) )
except Exception as e:
logger.error(f"Post-change hook for setting failed : {e}")
raise
def _load_current_values(self):
super()._load_current_values()
# Specific logic for those settings who are "virtual" settings
# and only meant to have a custom setter mapped to tools_rootpw
self.values["root_password"] = ""
self.values["root_password_confirm"] = ""
def get(self, key="", mode="classic"):
result = super().get(key=key, mode=mode)
if mode == "full":
for panel, section, option in self._iterate():
if m18n.key_exists(self.config["i18n"] + "_" + option["id"] + "_help"):
option["help"] = m18n.n(
self.config["i18n"] + "_" + option["id"] + "_help"
) )
unknown_settings[key] = value return self.config
except Exception as e:
raise YunohostValidationError("global_settings_cant_open_settings", reason=e) # Dirty hack to let settings_get() to work from a python script
if isinstance(result, str) and result in ["True", "False"]:
result = bool(result == "True")
return result
def reset(self, key="", operation_logger=None):
self.filter_key = key
# Read config panel toml
self._get_config_panel()
if not self.config:
raise YunohostValidationError("config_no_panel")
# Replace all values with default values
self.values = self._get_default_values()
Question.operation_logger = operation_logger
if operation_logger:
operation_logger.start()
if unknown_settings:
try: try:
_save_settings(unknown_settings, location=unknown_settings_path) self._apply()
_save_settings(settings) except YunohostError:
except Exception as e: raise
logger.warning(f"Failed to save unknown settings (because {e}), aborting.") # Script got manually interrupted ...
# N.B. : KeyboardInterrupt does not inherit from Exception
except (KeyboardInterrupt, EOFError):
error = m18n.n("operation_interrupted")
logger.error(m18n.n("config_apply_failed", error=error))
raise
# Something wrong happened in Yunohost's code (most probably hook_exec)
except Exception:
import traceback
return settings error = m18n.n("unexpected_error", error="\n" + traceback.format_exc())
logger.error(m18n.n("config_apply_failed", error=error))
raise
logger.success(m18n.n("global_settings_reset_success"))
def _save_settings(settings, location=SETTINGS_PATH): operation_logger.success()
settings_without_description = {}
for key, value in settings.items():
settings_without_description[key] = value
if "description" in value:
del settings_without_description[key]["description"]
try:
result = json.dumps(settings_without_description, indent=4)
except Exception as e:
raise YunohostError("global_settings_cant_serialize_settings", reason=e)
try:
with open(location, "w") as settings_fd:
settings_fd.write(result)
except Exception as e:
raise YunohostError("global_settings_cant_write_settings", reason=e)
# Meant to be a dict of setting_name -> function to call # Meant to be a dict of setting_name -> function to call
@ -370,13 +234,8 @@ post_change_hooks = {}
def post_change_hook(setting_name): def post_change_hook(setting_name):
# TODO: Check that setting_name exists
def decorator(func): def decorator(func):
assert (
setting_name in DEFAULTS.keys()
), f"The setting {setting_name} does not exists"
assert (
setting_name not in post_change_hooks
), f"You can only register one post change hook per setting (in particular for {setting_name})"
post_change_hooks[setting_name] = func post_change_hooks[setting_name] = func
return func return func
@ -404,48 +263,48 @@ def trigger_post_change_hook(setting_name, old_value, new_value):
# =========================================== # ===========================================
@post_change_hook("ssowat.panel_overlay.enabled") @post_change_hook("ssowat_panel_overlay_enabled")
@post_change_hook("security.nginx.redirect_to_https") @post_change_hook("nginx_redirect_to_https")
@post_change_hook("security.nginx.compatibility") @post_change_hook("nginx_compatibility")
@post_change_hook("security.webadmin.allowlist.enabled") @post_change_hook("webadmin_allowlist_enabled")
@post_change_hook("security.webadmin.allowlist") @post_change_hook("webadmin_allowlist")
def reconfigure_nginx(setting_name, old_value, new_value): def reconfigure_nginx(setting_name, old_value, new_value):
if old_value != new_value: if old_value != new_value:
regen_conf(names=["nginx"]) regen_conf(names=["nginx"])
@post_change_hook("security.experimental.enabled") @post_change_hook("security_experimental_enabled")
def reconfigure_nginx_and_yunohost(setting_name, old_value, new_value): def reconfigure_nginx_and_yunohost(setting_name, old_value, new_value):
if old_value != new_value: if old_value != new_value:
regen_conf(names=["nginx", "yunohost"]) regen_conf(names=["nginx", "yunohost"])
@post_change_hook("security.ssh.compatibility") @post_change_hook("ssh_compatibility")
@post_change_hook("security.ssh.password_authentication") @post_change_hook("ssh_password_authentication")
def reconfigure_ssh(setting_name, old_value, new_value): def reconfigure_ssh(setting_name, old_value, new_value):
if old_value != new_value: if old_value != new_value:
regen_conf(names=["ssh"]) regen_conf(names=["ssh"])
@post_change_hook("security.ssh.port") @post_change_hook("ssh_port")
def reconfigure_ssh_and_fail2ban(setting_name, old_value, new_value): def reconfigure_ssh_and_fail2ban(setting_name, old_value, new_value):
if old_value != new_value: if old_value != new_value:
regen_conf(names=["ssh", "fail2ban"]) regen_conf(names=["ssh", "fail2ban"])
firewall_reload() firewall_reload()
@post_change_hook("smtp.allow_ipv6") @post_change_hook("smtp_allow_ipv6")
@post_change_hook("smtp.relay.host") @post_change_hook("smtp_relay_host")
@post_change_hook("smtp.relay.port") @post_change_hook("smtp_relay_port")
@post_change_hook("smtp.relay.user") @post_change_hook("smtp_relay_user")
@post_change_hook("smtp.relay.password") @post_change_hook("smtp_relay_password")
@post_change_hook("security.postfix.compatibility") @post_change_hook("postfix_compatibility")
def reconfigure_postfix(setting_name, old_value, new_value): def reconfigure_postfix(setting_name, old_value, new_value):
if old_value != new_value: if old_value != new_value:
regen_conf(names=["postfix"]) regen_conf(names=["postfix"])
@post_change_hook("pop3.enabled") @post_change_hook("pop3_enabled")
def reconfigure_dovecot(setting_name, old_value, new_value): def reconfigure_dovecot(setting_name, old_value, new_value):
dovecot_package = "dovecot-pop3d" dovecot_package = "dovecot-pop3d"

View file

@ -1,4 +1,21 @@
# encoding: utf-8 #
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import re import re
import os import os
@ -158,15 +175,6 @@ def _get_user_for_ssh(username, attrs=None):
"home_path": root_unix.pw_dir, "home_path": root_unix.pw_dir,
} }
if username == "admin":
admin_unix = pwd.getpwnam("admin")
return {
"username": "admin",
"fullname": "",
"mail": "",
"home_path": admin_unix.pw_dir,
}
# TODO escape input using https://www.python-ldap.org/doc/html/ldap-filter.html # TODO escape input using https://www.python-ldap.org/doc/html/ldap-filter.html
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface

View file

@ -102,7 +102,7 @@ def config_app(request):
def test_app_config_get(config_app): def test_app_config_get(config_app):
user_create("alice", "Alice", "White", _get_maindomain(), "test123Ynh") user_create("alice", _get_maindomain(), "test123Ynh", fullname="Alice White")
assert isinstance(app_config_get(config_app), dict) assert isinstance(app_config_get(config_app), dict)
assert isinstance(app_config_get(config_app, full=True), dict) assert isinstance(app_config_get(config_app, full=True), dict)

View file

@ -0,0 +1,392 @@
import os
import pytest
from moulinette.utils.process import check_output
from yunohost.app import app_setting
from yunohost.domain import _get_maindomain
from yunohost.utils.resources import AppResource, AppResourceManager, AppResourceClassesByType
from yunohost.permission import user_permission_list, permission_delete
dummyfile = "/tmp/dummyappresource-testapp"
class DummyAppResource(AppResource):
type = "dummy"
default_properties = {
"file": "/tmp/dummyappresource-__APP__",
"content": "foo",
}
def provision_or_update(self, context):
open(self.file, "w").write(self.content)
if self.content == "forbiddenvalue":
raise Exception("Emeged you used the forbidden value!1!£&")
def deprovision(self, context):
os.system(f"rm -f {self.file}")
AppResourceClassesByType["dummy"] = DummyAppResource
def setup_function(function):
clean()
os.system("mkdir /etc/yunohost/apps/testapp")
os.system("echo 'id: testapp' > /etc/yunohost/apps/testapp/settings.yml")
os.system("echo 'packaging_format = 2' > /etc/yunohost/apps/testapp/manifest.toml")
os.system("echo 'id = \"testapp\"' >> /etc/yunohost/apps/testapp/manifest.toml")
def teardown_function(function):
clean()
def clean():
os.system(f"rm -f {dummyfile}")
os.system("rm -rf /etc/yunohost/apps/testapp")
os.system("rm -rf /var/www/testapp")
os.system("rm -rf /home/yunohost.app/testapp")
os.system("apt remove lolcat sl nyancat yarn >/dev/null 2>/dev/null")
os.system("userdel testapp 2>/dev/null")
for p in user_permission_list()["permissions"]:
if p.startswith("testapp."):
permission_delete(p, force=True, sync_perm=False)
def test_provision_dummy():
current = {"resources": {}}
wanted = {"resources": {"dummy": {}}}
assert not os.path.exists(dummyfile)
AppResourceManager("testapp", current=current, wanted=wanted).apply(rollback_if_failure=False)
assert open(dummyfile).read().strip() == "foo"
def test_deprovision_dummy():
current = {"resources": {"dummy": {}}}
wanted = {"resources": {}}
open(dummyfile, "w").write("foo")
assert open(dummyfile).read().strip() == "foo"
AppResourceManager("testapp", current=current, wanted=wanted).apply(rollback_if_failure=False)
assert not os.path.exists(dummyfile)
def test_provision_dummy_nondefaultvalue():
current = {"resources": {}}
wanted = {"resources": {"dummy": {"content": "bar"}}}
assert not os.path.exists(dummyfile)
AppResourceManager("testapp", current=current, wanted=wanted).apply(rollback_if_failure=False)
assert open(dummyfile).read().strip() == "bar"
def test_update_dummy():
current = {"resources": {"dummy": {}}}
wanted = {"resources": {"dummy": {"content": "bar"}}}
open(dummyfile, "w").write("foo")
assert open(dummyfile).read().strip() == "foo"
AppResourceManager("testapp", current=current, wanted=wanted).apply(rollback_if_failure=False)
assert open(dummyfile).read().strip() == "bar"
def test_update_dummy_fail():
current = {"resources": {"dummy": {}}}
wanted = {"resources": {"dummy": {"content": "forbiddenvalue"}}}
open(dummyfile, "w").write("foo")
assert open(dummyfile).read().strip() == "foo"
with pytest.raises(Exception):
AppResourceManager("testapp", current=current, wanted=wanted).apply(rollback_if_failure=False)
assert open(dummyfile).read().strip() == "forbiddenvalue"
def test_update_dummy_failwithrollback():
current = {"resources": {"dummy": {}}}
wanted = {"resources": {"dummy": {"content": "forbiddenvalue"}}}
open(dummyfile, "w").write("foo")
assert open(dummyfile).read().strip() == "foo"
with pytest.raises(Exception):
AppResourceManager("testapp", current=current, wanted=wanted).apply(rollback_if_failure=True)
assert open(dummyfile).read().strip() == "foo"
def test_resource_system_user():
r = AppResourceClassesByType["system_user"]
conf = {}
assert os.system("getent passwd testapp 2>/dev/null") != 0
r(conf, "testapp").provision_or_update()
assert os.system("getent passwd testapp >/dev/null") == 0
assert os.system("groups testapp | grep -q 'sftp.app'") != 0
conf["allow_sftp"] = True
r(conf, "testapp").provision_or_update()
assert os.system("getent passwd testapp >/dev/null") == 0
assert os.system("groups testapp | grep -q 'sftp.app'") == 0
r(conf, "testapp").deprovision()
assert os.system("getent passwd testapp 2>/dev/null") != 0
def test_resource_install_dir():
r = AppResourceClassesByType["install_dir"]
conf = {"owner": "nobody:rx", "group": "nogroup:rx"}
# FIXME: should also check settings ?
# FIXME: should also check automigrate from final_path
# FIXME: should also test changing the install folder location ?
assert not os.path.exists("/var/www/testapp")
r(conf, "testapp").provision_or_update()
assert os.path.exists("/var/www/testapp")
unixperms = check_output("ls -ld /var/www/testapp").split()
assert unixperms[0] == "dr-xr-x---"
assert unixperms[2] == "nobody"
assert unixperms[3] == "nogroup"
conf["owner"] = "nobody:rwx"
conf["group"] = "www-data:x"
r(conf, "testapp").provision_or_update()
assert os.path.exists("/var/www/testapp")
unixperms = check_output("ls -ld /var/www/testapp").split()
assert unixperms[0] == "drwx--x---"
assert unixperms[2] == "nobody"
assert unixperms[3] == "www-data"
r(conf, "testapp").deprovision()
assert not os.path.exists("/var/www/testapp")
def test_resource_data_dir():
r = AppResourceClassesByType["data_dir"]
conf = {"owner": "nobody:rx", "group": "nogroup:rx"}
assert not os.path.exists("/home/yunohost.app/testapp")
r(conf, "testapp").provision_or_update()
assert os.path.exists("/home/yunohost.app/testapp")
unixperms = check_output("ls -ld /home/yunohost.app/testapp").split()
assert unixperms[0] == "dr-xr-x---"
assert unixperms[2] == "nobody"
assert unixperms[3] == "nogroup"
conf["owner"] = "nobody:rwx"
conf["group"] = "www-data:x"
r(conf, "testapp").provision_or_update()
assert os.path.exists("/home/yunohost.app/testapp")
unixperms = check_output("ls -ld /home/yunohost.app/testapp").split()
assert unixperms[0] == "drwx--x---"
assert unixperms[2] == "nobody"
assert unixperms[3] == "www-data"
r(conf, "testapp").deprovision()
# FIXME : implement and check purge option
#assert not os.path.exists("/home/yunohost.app/testapp")
def test_resource_ports():
r = AppResourceClassesByType["ports"]
conf = {}
assert not app_setting("testapp", "port")
r(conf, "testapp").provision_or_update()
assert app_setting("testapp", "port")
r(conf, "testapp").deprovision()
assert not app_setting("testapp", "port")
def test_resource_ports_several():
r = AppResourceClassesByType["ports"]
conf = {"main": {"default": 12345}, "foobar": {"default": 23456}}
assert not app_setting("testapp", "port")
assert not app_setting("testapp", "port_foobar")
r(conf, "testapp").provision_or_update()
assert app_setting("testapp", "port")
assert app_setting("testapp", "port_foobar")
r(conf, "testapp").deprovision()
assert not app_setting("testapp", "port")
assert not app_setting("testapp", "port_foobar")
def test_resource_database():
r = AppResourceClassesByType["database"]
conf = {"type": "mysql"}
assert os.system("mysqlshow 'testapp' >/dev/null 2>/dev/null") != 0
assert not app_setting("testapp", "db_name")
assert not app_setting("testapp", "db_user")
assert not app_setting("testapp", "db_pwd")
r(conf, "testapp").provision_or_update()
assert os.system("mysqlshow 'testapp' >/dev/null 2>/dev/null") == 0
assert app_setting("testapp", "db_name")
assert app_setting("testapp", "db_user")
assert app_setting("testapp", "db_pwd")
r(conf, "testapp").deprovision()
assert os.system("mysqlshow 'testapp' >/dev/null 2>/dev/null") != 0
assert not app_setting("testapp", "db_name")
assert not app_setting("testapp", "db_user")
assert not app_setting("testapp", "db_pwd")
def test_resource_apt():
r = AppResourceClassesByType["apt"]
conf = {
"packages": "nyancat, sl",
"extras": {
"yarn": {
"repo": "deb https://dl.yarnpkg.com/debian/ stable main",
"key": "https://dl.yarnpkg.com/debian/pubkey.gpg",
"packages": "yarn",
}
}
}
assert os.system("dpkg --list | grep -q 'ii *nyancat '") != 0
assert os.system("dpkg --list | grep -q 'ii *sl '") != 0
assert os.system("dpkg --list | grep -q 'ii *yarn '") != 0
assert os.system("dpkg --list | grep -q 'ii *lolcat '") != 0
assert os.system("dpkg --list | grep -q 'ii *testapp-ynh-deps '") != 0
r(conf, "testapp").provision_or_update()
assert os.system("dpkg --list | grep -q 'ii *nyancat '") == 0
assert os.system("dpkg --list | grep -q 'ii *sl '") == 0
assert os.system("dpkg --list | grep -q 'ii *yarn '") == 0
assert os.system("dpkg --list | grep -q 'ii *lolcat '") != 0 # Lolcat shouldnt be installed yet
assert os.system("dpkg --list | grep -q 'ii *testapp-ynh-deps '") == 0
conf["packages"] += ", lolcat"
r(conf, "testapp").provision_or_update()
assert os.system("dpkg --list | grep -q 'ii *nyancat '") == 0
assert os.system("dpkg --list | grep -q 'ii *sl '") == 0
assert os.system("dpkg --list | grep -q 'ii *yarn '") == 0
assert os.system("dpkg --list | grep -q 'ii *lolcat '") == 0
assert os.system("dpkg --list | grep -q 'ii *testapp-ynh-deps '") == 0
r(conf, "testapp").deprovision()
assert os.system("dpkg --list | grep -q 'ii *nyancat '") != 0
assert os.system("dpkg --list | grep -q 'ii *sl '") != 0
assert os.system("dpkg --list | grep -q 'ii *yarn '") != 0
assert os.system("dpkg --list | grep -q 'ii *lolcat '") != 0
assert os.system("dpkg --list | grep -q 'ii *testapp-ynh-deps '") != 0
def test_resource_permissions():
maindomain = _get_maindomain()
os.system(f"echo 'domain: {maindomain}' >> /etc/yunohost/apps/testapp/settings.yml")
os.system("echo 'path: /testapp' >> /etc/yunohost/apps/testapp/settings.yml")
# A manager object is required to set the label of the app...
manager = AppResourceManager("testapp", current={}, wanted={"name": "Test App"})
r = AppResourceClassesByType["permissions"]
conf = {
"main": {
"url": "/",
"allowed": "visitors"
# TODO: test protected?
},
}
res = user_permission_list(full=True)["permissions"]
assert not any(key.startswith("testapp.") for key in res)
r(conf, "testapp", manager).provision_or_update()
res = user_permission_list(full=True)["permissions"]
assert "testapp.main" in res
assert "visitors" in res["testapp.main"]["allowed"]
assert res["testapp.main"]["url"] == "/"
assert "testapp.admin" not in res
conf["admin"] = {
"url": "/admin",
"allowed": ""
}
r(conf, "testapp", manager).provision_or_update()
res = user_permission_list(full=True)["permissions"]
assert "testapp.main" in list(res.keys())
assert "visitors" in res["testapp.main"]["allowed"]
assert res["testapp.main"]["url"] == "/"
assert "testapp.admin" in res
assert not res["testapp.admin"]["allowed"]
assert res["testapp.admin"]["url"] == "/admin"
conf["admin"]["url"] = "/adminpanel"
r(conf, "testapp", manager).provision_or_update()
res = user_permission_list(full=True)["permissions"]
# FIXME FIXME FIXME : this is the current behavior but
# it is NOT okay. c.f. comment in the code
assert res["testapp.admin"]["url"] == "/admin" # should be '/adminpanel'
r(conf, "testapp").deprovision()
res = user_permission_list(full=True)["permissions"]
assert "testapp.main" not in res

View file

@ -15,6 +15,8 @@ from yunohost.app import (
_is_installed, _is_installed,
app_upgrade, app_upgrade,
app_map, app_map,
app_manifest,
app_info,
) )
from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list
from yunohost.utils.error import YunohostError from yunohost.utils.error import YunohostError
@ -45,6 +47,7 @@ def clean():
"break_yo_system", "break_yo_system",
"legacy_app", "legacy_app",
"legacy_app__2", "legacy_app__2",
"manifestv2_app",
"full_domain_app", "full_domain_app",
"my_webapp", "my_webapp",
] ]
@ -115,7 +118,10 @@ def app_expected_files(domain, app):
if app.startswith("legacy_app"): if app.startswith("legacy_app"):
yield "/var/www/%s/index.html" % app yield "/var/www/%s/index.html" % app
yield "/etc/yunohost/apps/%s/settings.yml" % app yield "/etc/yunohost/apps/%s/settings.yml" % app
yield "/etc/yunohost/apps/%s/manifest.json" % app if "manifestv2" in app:
yield "/etc/yunohost/apps/%s/manifest.toml" % app
else:
yield "/etc/yunohost/apps/%s/manifest.json" % app
yield "/etc/yunohost/apps/%s/scripts/install" % app yield "/etc/yunohost/apps/%s/scripts/install" % app
yield "/etc/yunohost/apps/%s/scripts/remove" % app yield "/etc/yunohost/apps/%s/scripts/remove" % app
@ -157,6 +163,15 @@ def install_legacy_app(domain, path, public=True):
) )
def install_manifestv2_app(domain, path, public=True):
app_install(
os.path.join(get_test_apps_dir(), "manifestv2_app_ynh"),
args="domain={}&path={}&init_main_permission={}".format(domain, path, "visitors" if public else "all_users"),
force=True,
)
def install_full_domain_app(domain): def install_full_domain_app(domain):
app_install( app_install(
@ -195,6 +210,105 @@ def test_legacy_app_install_main_domain():
assert app_is_not_installed(main_domain, "legacy_app") assert app_is_not_installed(main_domain, "legacy_app")
def test_legacy_app_manifest_preinstall():
m = app_manifest(os.path.join(get_test_apps_dir(), "legacy_app_ynh"))
# v1 manifesto are expected to have been autoconverted to v2
assert "id" in m
assert "description" in m
assert "integration" in m
assert "install" in m
assert m["doc"] == {}
assert m["notifications"] == {"pre_install": {}, "pre_upgrade": {}, "post_install": {}, "post_upgrade": {}}
def test_manifestv2_app_manifest_preinstall():
m = app_manifest(os.path.join(get_test_apps_dir(), "manifestv2_app_ynh"))
assert "id" in m
assert "install" in m
assert "description" in m
assert "doc" in m
assert "This is a dummy description of this app features" in m["doc"]["DESCRIPTION"]["en"]
assert "Ceci est une fausse description des fonctionalités de l'app" in m["doc"]["DESCRIPTION"]["fr"]
assert "notifications" in m
assert "This is a dummy disclaimer to display prior to the install" in m["notifications"]["pre_install"]["main"]["en"]
assert "Ceci est un faux disclaimer à présenter avant l'installation" in m["notifications"]["pre_install"]["main"]["fr"]
def test_manifestv2_app_install_main_domain():
main_domain = _get_maindomain()
install_manifestv2_app(main_domain, "/manifestv2")
app_map_ = app_map(raw=True)
assert main_domain in app_map_
assert "/manifestv2" in app_map_[main_domain]
assert "id" in app_map_[main_domain]["/manifestv2"]
assert app_map_[main_domain]["/manifestv2"]["id"] == "manifestv2_app"
assert app_is_installed(main_domain, "manifestv2_app")
assert app_is_exposed_on_http(main_domain, "/manifestv2", "Hextris")
app_remove("manifestv2_app")
assert app_is_not_installed(main_domain, "manifestv2_app")
def test_manifestv2_app_info_postinstall():
main_domain = _get_maindomain()
install_manifestv2_app(main_domain, "/manifestv2")
m = app_info("manifestv2_app", full=True)["manifest"]
assert "id" in m
assert "install" in m
assert "description" in m
assert "doc" in m
assert "The app install dir is /var/www/manifestv2_app" in m["doc"]["ADMIN"]["en"]
assert "Le dossier d'install de l'app est /var/www/manifestv2_app" in m["doc"]["ADMIN"]["fr"]
assert "notifications" in m
assert "The app install dir is /var/www/manifestv2_app" in m["notifications"]["post_install"]["main"]["en"]
assert "The app id is manifestv2_app" in m["notifications"]["post_install"]["main"]["en"]
assert f"The app url is {main_domain}/manifestv2" in m["notifications"]["post_install"]["main"]["en"]
def test_manifestv2_app_info_preupgrade(monkeypatch):
manifest = app_manifest(os.path.join(get_test_apps_dir(), "manifestv2_app_ynh"))
from yunohost.app_catalog import _load_apps_catalog as original_load_apps_catalog
def custom_load_apps_catalog(*args, **kwargs):
res = original_load_apps_catalog(*args, **kwargs)
res["apps"]["manifestv2_app"] = {
"id": "manifestv2_app",
"level": 10,
"lastUpdate": 999999999,
"maintained": True,
"manifest": manifest,
"state": "working",
}
res["apps"]["manifestv2_app"]["manifest"]["version"] = "99999~ynh1"
return res
monkeypatch.setattr("yunohost.app._load_apps_catalog", custom_load_apps_catalog)
main_domain = _get_maindomain()
install_manifestv2_app(main_domain, "/manifestv2")
i = app_info("manifestv2_app", full=True)
assert i["upgradable"] == "yes"
assert i["new_version"] == "99999~ynh1"
# FIXME : as I write this test, I realize that this implies the catalog API
# does provide the notifications, which means the list builder script
# should parse the files in the original app repo, possibly with proper i18n etc
assert "This is a dummy disclaimer to display prior to any upgrade" \
in i["from_catalog"]["manifest"]["notifications"]["pre_upgrade"]["main"]["en"]
def test_app_from_catalog(): def test_app_from_catalog():
main_domain = _get_maindomain() main_domain = _get_maindomain()

View file

@ -77,7 +77,7 @@ def setup_function(function):
if "with_permission_app_installed" in markers: if "with_permission_app_installed" in markers:
assert not app_is_installed("permissions_app") assert not app_is_installed("permissions_app")
user_create("alice", "Alice", "White", maindomain, "test123Ynh") user_create("alice", maindomain, "test123Ynh", fullname="Alice White")
with patch.object(os, "isatty", return_value=False): with patch.object(os, "isatty", return_value=False):
install_app("permissions_app_ynh", "/urlpermissionapp" "&admin=alice") install_app("permissions_app_ynh", "/urlpermissionapp" "&admin=alice")
assert app_is_installed("permissions_app") assert app_is_installed("permissions_app")
@ -355,13 +355,13 @@ def test_backup_script_failure_handling(monkeypatch, mocker):
@pytest.mark.with_backup_recommended_app_installed @pytest.mark.with_backup_recommended_app_installed
def test_backup_not_enough_free_space(monkeypatch, mocker): def test_backup_not_enough_free_space(monkeypatch, mocker):
def custom_disk_usage(path): def custom_space_used_by_directory(path, *args, **kwargs):
return 99999999999999999 return 99999999999999999
def custom_free_space_in_directory(dirpath): def custom_free_space_in_directory(dirpath):
return 0 return 0
monkeypatch.setattr("yunohost.backup.disk_usage", custom_disk_usage) monkeypatch.setattr("yunohost.backup.space_used_by_directory", custom_space_used_by_directory)
monkeypatch.setattr( monkeypatch.setattr(
"yunohost.backup.free_space_in_directory", custom_free_space_in_directory "yunohost.backup.free_space_in_directory", custom_free_space_in_directory
) )

View file

@ -2,7 +2,8 @@ import pytest
import os import os
from yunohost.authenticators.ldap_admin import Authenticator as LDAPAuth from yunohost.authenticators.ldap_admin import Authenticator as LDAPAuth
from yunohost.tools import tools_adminpw from yunohost.user import user_create, user_list, user_update, user_delete
from yunohost.domain import _get_maindomain
from moulinette import m18n from moulinette import m18n
from moulinette.core import MoulinetteError from moulinette.core import MoulinetteError
@ -10,50 +11,75 @@ from moulinette.core import MoulinetteError
def setup_function(function): def setup_function(function):
if os.system("systemctl is-active slapd") != 0: for u in user_list()["users"]:
user_delete(u, purge=True)
maindomain = _get_maindomain()
if os.system("systemctl is-active slapd >/dev/null") != 0:
os.system("systemctl start slapd && sleep 3") os.system("systemctl start slapd && sleep 3")
tools_adminpw("yunohost", check_strength=False) user_create("alice", maindomain, "Yunohost", admin=True, fullname="Alice White")
user_create("bob", maindomain, "test123Ynh", fullname="Bob Snow")
def teardown_function():
os.system("systemctl is-active slapd >/dev/null || systemctl start slapd; sleep 5")
for u in user_list()["users"]:
user_delete(u, purge=True)
def test_authenticate(): def test_authenticate():
LDAPAuth().authenticate_credentials(credentials="yunohost") LDAPAuth().authenticate_credentials(credentials="alice:Yunohost")
def test_authenticate_with_no_user():
with pytest.raises(MoulinetteError):
LDAPAuth().authenticate_credentials(credentials="Yunohost")
with pytest.raises(MoulinetteError):
LDAPAuth().authenticate_credentials(credentials=":Yunohost")
def test_authenticate_with_user_who_is_not_admin():
with pytest.raises(MoulinetteError) as exception:
LDAPAuth().authenticate_credentials(credentials="bob:test123Ynh")
translation = m18n.n("invalid_credentials")
expected_msg = translation.format()
assert expected_msg in str(exception)
def test_authenticate_with_wrong_password(): def test_authenticate_with_wrong_password():
with pytest.raises(MoulinetteError) as exception: with pytest.raises(MoulinetteError) as exception:
LDAPAuth().authenticate_credentials(credentials="bad_password_lul") LDAPAuth().authenticate_credentials(credentials="alice:bad_password_lul")
translation = m18n.n("invalid_password") translation = m18n.n("invalid_credentials")
expected_msg = translation.format() expected_msg = translation.format()
assert expected_msg in str(exception) assert expected_msg in str(exception)
def test_authenticate_server_down(mocker): def test_authenticate_server_down(mocker):
os.system("systemctl stop slapd && sleep 3") os.system("systemctl stop slapd && sleep 5")
# Now if slapd is down, moulinette tries to restart it LDAPAuth().authenticate_credentials(credentials="alice:Yunohost")
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(): def test_authenticate_change_password():
LDAPAuth().authenticate_credentials(credentials="yunohost") LDAPAuth().authenticate_credentials(credentials="alice:Yunohost")
tools_adminpw("plopette", check_strength=False) user_update("alice", change_password="plopette")
with pytest.raises(MoulinetteError) as exception: with pytest.raises(MoulinetteError) as exception:
LDAPAuth().authenticate_credentials(credentials="yunohost") LDAPAuth().authenticate_credentials(credentials="alice:Yunohost")
translation = m18n.n("invalid_password") translation = m18n.n("invalid_credentials")
expected_msg = translation.format() expected_msg = translation.format()
assert expected_msg in str(exception) assert expected_msg in str(exception)
LDAPAuth().authenticate_credentials(credentials="plopette") LDAPAuth().authenticate_credentials(credentials="alice:plopette")

View file

@ -78,6 +78,7 @@ def _permission_create_with_dummy_app(
"name": app, "name": app,
"id": app, "id": app,
"description": {"en": "Dummy app to test permissions"}, "description": {"en": "Dummy app to test permissions"},
"arguments": {"install": []}
}, },
f, f,
) )
@ -108,7 +109,7 @@ def clean_user_groups_permission():
user_delete(u) user_delete(u)
for g in user_group_list()["groups"]: for g in user_group_list()["groups"]:
if g not in ["all_users", "visitors"]: if g not in ["all_users", "visitors", "admins"]:
user_group_delete(g) user_group_delete(g)
for p in user_permission_list()["permissions"]: for p in user_permission_list()["permissions"]:
@ -157,8 +158,8 @@ def setup_function(function):
socket.getaddrinfo = new_getaddrinfo socket.getaddrinfo = new_getaddrinfo
user_create("alice", "Alice", "White", maindomain, dummy_password) user_create("alice", maindomain, dummy_password, fullname="Alice White")
user_create("bob", "Bob", "Snow", maindomain, dummy_password) user_create("bob", maindomain, dummy_password, fullname="Bob Snow")
_permission_create_with_dummy_app( _permission_create_with_dummy_app(
permission="wiki.main", permission="wiki.main",
url="/", url="/",

File diff suppressed because it is too large Load diff

View file

@ -3,177 +3,213 @@ import json
import glob import glob
import pytest import pytest
from yunohost.utils.error import YunohostError import moulinette
from yunohost.utils.error import YunohostError, YunohostValidationError
import yunohost.settings as settings
from yunohost.settings import ( from yunohost.settings import (
settings_get, settings_get,
settings_list, settings_list,
_get_settings,
settings_set, settings_set,
settings_reset, settings_reset,
settings_reset_all, settings_reset_all,
SETTINGS_PATH_OTHER_LOCATION, SETTINGS_PATH
SETTINGS_PATH,
DEFAULTS,
) )
DEFAULTS["example.bool"] = {"type": "bool", "default": True} EXAMPLE_SETTINGS = """
DEFAULTS["example.int"] = {"type": "int", "default": 42} [example]
DEFAULTS["example.string"] = {"type": "string", "default": "yolo swag"} [example.example]
DEFAULTS["example.enum"] = {"type": "enum", "default": "a", "choices": ["a", "b", "c"]} [example.example.boolean]
type = "boolean"
yes = "True"
no = "False"
default = "True"
[example.example.number]
type = "number"
default = 42
[example.example.string]
type = "string"
default = "yolo swag"
[example.example.select]
type = "select"
choices = ["a", "b", "c"]
default = "a"
"""
def setup_function(function): def setup_function(function):
os.system("mv /etc/yunohost/settings.json /etc/yunohost/settings.json.saved") # Backup settings
if os.path.exists(SETTINGS_PATH):
os.system(f"mv {SETTINGS_PATH} {SETTINGS_PATH}.saved")
# Add example settings to config panel
os.system("cp /usr/share/yunohost/config_global.toml /usr/share/yunohost/config_global.toml.saved")
with open("/usr/share/yunohost/config_global.toml", "a") as file:
file.write(EXAMPLE_SETTINGS)
def teardown_function(function): def teardown_function(function):
os.system("mv /etc/yunohost/settings.json.saved /etc/yunohost/settings.json") if os.path.exists("/etc/yunohost/settings.yml.saved"):
for filename in glob.glob("/etc/yunohost/settings-*.json"): os.system(f"mv {SETTINGS_PATH}.saved {SETTINGS_PATH}")
os.remove(filename) elif os.path.exists(SETTINGS_PATH):
os.remove(SETTINGS_PATH)
os.system("mv /usr/share/yunohost/config_global.toml.saved /usr/share/yunohost/config_global.toml")
def monkey_get_setting_description(key): old_translate = moulinette.core.Translator.translate
return "Dummy %s setting" % key.split(".")[-1]
def _monkeypatch_translator(self, key, *args, **kwargs):
if key.startswith("global_settings_setting_"):
return f"Dummy translation for {key}"
return old_translate(self, key, *args, **kwargs)
moulinette.core.Translator.translate = _monkeypatch_translator
settings._get_setting_description = monkey_get_setting_description def _get_settings():
return yaml.load(open(SETTINGS_PATH, "r"))
def test_settings_get_bool(): def test_settings_get_bool():
assert settings_get("example.bool") assert settings_get("example.example.boolean")
def test_settings_get_full_bool(): # FIXME : Testing this doesn't make sense ? This should be tested in test_config.py ?
assert settings_get("example.bool", True) == { #def test_settings_get_full_bool():
"type": "bool", # assert settings_get("example.example.boolean", True) == {'version': '1.0',
"value": True, # 'i18n': 'global_settings_setting',
"default": True, # 'panels': [{'services': [],
"description": "Dummy bool setting", # 'actions': {'apply': {'en': 'Apply'}},
} # 'sections': [{'name': '',
# 'services': [],
# 'optional': True,
# 'options': [{'type': 'boolean',
# 'yes': 'True',
# 'no': 'False',
# 'default': 'True',
# 'id': 'boolean',
# 'name': 'boolean',
# 'optional': True,
# 'current_value': 'True',
# 'ask': 'global_settings_setting_boolean',
# 'choices': []}],
# 'id': 'example'}],
# 'id': 'example',
# 'name': {'en': 'Example'}}]}
def test_settings_get_int(): def test_settings_get_int():
assert settings_get("example.int") == 42 assert settings_get("example.example.number") == 42
def test_settings_get_full_int(): #def test_settings_get_full_int():
assert settings_get("example.int", True) == { # assert settings_get("example.int", True) == {
"type": "int", # "type": "int",
"value": 42, # "value": 42,
"default": 42, # "default": 42,
"description": "Dummy int setting", # "description": "Dummy int setting",
} # }
def test_settings_get_string(): def test_settings_get_string():
assert settings_get("example.string") == "yolo swag" assert settings_get("example.example.string") == "yolo swag"
def test_settings_get_full_string(): #def test_settings_get_full_string():
assert settings_get("example.string", True) == { # assert settings_get("example.example.string", True) == {
"type": "string", # "type": "string",
"value": "yolo swag", # "value": "yolo swag",
"default": "yolo swag", # "default": "yolo swag",
"description": "Dummy string setting", # "description": "Dummy string setting",
} # }
def test_settings_get_enum(): def test_settings_get_select():
assert settings_get("example.enum") == "a" assert settings_get("example.example.select") == "a"
def test_settings_get_full_enum(): #def test_settings_get_full_select():
assert settings_get("example.enum", True) == { # option = settings_get("example.example.select", full=True).get('panels')[0].get('sections')[0].get('options')[0]
"type": "enum", # assert option.get('choices') == ["a", "b", "c"]
"value": "a",
"default": "a",
"description": "Dummy enum setting",
"choices": ["a", "b", "c"],
}
def test_settings_get_doesnt_exists(): def test_settings_get_doesnt_exists():
with pytest.raises(YunohostError): with pytest.raises(YunohostValidationError):
settings_get("doesnt.exists") settings_get("doesnt.exists")
def test_settings_list(): #def test_settings_list():
assert settings_list() == _get_settings() # assert settings_list() == _get_settings()
def test_settings_set(): def test_settings_set():
settings_set("example.bool", False) settings_set("example.example.boolean", False)
assert settings_get("example.bool") is False assert settings_get("example.example.boolean") is False
settings_set("example.bool", "on") settings_set("example.example.boolean", "on")
assert settings_get("example.bool") is True assert settings_get("example.example.boolean") is True
def test_settings_set_int(): def test_settings_set_int():
settings_set("example.int", 21) settings_set("example.example.number", 21)
assert settings_get("example.int") == 21 assert settings_get("example.example.number") == 21
def test_settings_set_enum(): def test_settings_set_select():
settings_set("example.enum", "c") settings_set("example.example.select", "c")
assert settings_get("example.enum") == "c" assert settings_get("example.example.select") == "c"
def test_settings_set_doesexit(): def test_settings_set_doesexit():
with pytest.raises(YunohostError): with pytest.raises(YunohostValidationError):
settings_set("doesnt.exist", True) settings_set("doesnt.exist", True)
def test_settings_set_bad_type_bool(): def test_settings_set_bad_type_bool():
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
settings_set("example.bool", 42) settings_set("example.example.boolean", 42)
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
settings_set("example.bool", "pouet") settings_set("example.example.boolean", "pouet")
def test_settings_set_bad_type_int(): def test_settings_set_bad_type_int():
# with pytest.raises(YunohostError):
# settings_set("example.example.number", True)
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
settings_set("example.int", True) settings_set("example.example.number", "pouet")
with pytest.raises(YunohostError):
settings_set("example.int", "pouet")
def test_settings_set_bad_type_string(): #def test_settings_set_bad_type_string():
with pytest.raises(YunohostError): # with pytest.raises(YunohostError):
settings_set("example.string", True) # settings_set("example.example.string", True)
with pytest.raises(YunohostError): # with pytest.raises(YunohostError):
settings_set("example.string", 42) # settings_set("example.example.string", 42)
def test_settings_set_bad_value_enum(): def test_settings_set_bad_value_select():
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
settings_set("example.enum", True) settings_set("example.example.select", True)
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
settings_set("example.enum", "e") settings_set("example.example.select", "e")
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
settings_set("example.enum", 42) settings_set("example.example.select", 42)
with pytest.raises(YunohostError): with pytest.raises(YunohostError):
settings_set("example.enum", "pouet") settings_set("example.example.select", "pouet")
def test_settings_list_modified(): def test_settings_list_modified():
settings_set("example.int", 21) settings_set("example.example.number", 21)
assert settings_list()["example.int"] == { assert settings_list()["number"] == 21
"default": 42,
"description": "Dummy int setting",
"type": "int",
"value": 21,
}
def test_reset(): def test_reset():
settings_set("example.int", 21) option = settings_get("example.example.number", full=True).get('panels')[0].get('sections')[0].get('options')[0]
assert settings_get("example.int") == 21 settings_set("example.example.number", 21)
settings_reset("example.int") assert settings_get("example.example.number") == 21
assert settings_get("example.int") == settings_get("example.int", True)["default"] settings_reset("example.example.number")
assert settings_get("example.example.number") == option["default"]
def test_settings_reset_doesexit(): def test_settings_reset_doesexit():
@ -183,10 +219,10 @@ def test_settings_reset_doesexit():
def test_reset_all(): def test_reset_all():
settings_before = settings_list() settings_before = settings_list()
settings_set("example.bool", False) settings_set("example.example.boolean", False)
settings_set("example.int", 21) settings_set("example.example.number", 21)
settings_set("example.string", "pif paf pouf") settings_set("example.example.string", "pif paf pouf")
settings_set("example.enum", "c") settings_set("example.example.select", "c")
assert settings_before != settings_list() assert settings_before != settings_list()
settings_reset_all() settings_reset_all()
if settings_before != settings_list(): if settings_before != settings_list():
@ -194,30 +230,30 @@ def test_reset_all():
assert settings_before[i] == settings_list()[i] assert settings_before[i] == settings_list()[i]
def test_reset_all_backup(): #def test_reset_all_backup():
settings_before = settings_list() # settings_before = settings_list()
settings_set("example.bool", False) # settings_set("example.bool", False)
settings_set("example.int", 21) # settings_set("example.int", 21)
settings_set("example.string", "pif paf pouf") # settings_set("example.string", "pif paf pouf")
settings_set("example.enum", "c") # settings_set("example.select", "c")
settings_after_modification = settings_list() # settings_after_modification = settings_list()
assert settings_before != settings_after_modification # assert settings_before != settings_after_modification
old_settings_backup_path = settings_reset_all()["old_settings_backup_path"] # old_settings_backup_path = settings_reset_all()["old_settings_backup_path"]
#
for i in settings_after_modification: # for i in settings_after_modification:
del settings_after_modification[i]["description"] # del settings_after_modification[i]["description"]
#
assert settings_after_modification == json.load(open(old_settings_backup_path, "r")) # assert settings_after_modification == json.load(open(old_settings_backup_path, "r"))
def test_unknown_keys(): #def test_unknown_keys():
unknown_settings_path = SETTINGS_PATH_OTHER_LOCATION % "unknown" # unknown_settings_path = SETTINGS_PATH_OTHER_LOCATION % "unknown"
unknown_setting = { # unknown_setting = {
"unkown_key": {"value": 42, "default": 31, "type": "int"}, # "unkown_key": {"value": 42, "default": 31, "type": "int"},
} # }
open(SETTINGS_PATH, "w").write(json.dumps(unknown_setting)) # open(SETTINGS_PATH, "w").write(json.dumps(unknown_setting))
#
# stimulate a write # # stimulate a write
settings_reset_all() # settings_reset_all()
#
assert unknown_setting == json.load(open(unknown_settings_path, "r")) # assert unknown_setting == json.load(open(unknown_settings_path, "r"))

View file

@ -11,7 +11,6 @@ from yunohost.user import (
user_import, user_import,
user_export, user_export,
FIELDS_FOR_IMPORT, FIELDS_FOR_IMPORT,
FIRST_ALIASES,
user_group_list, user_group_list,
user_group_create, user_group_create,
user_group_delete, user_group_delete,
@ -29,7 +28,7 @@ def clean_user_groups():
user_delete(u, purge=True) user_delete(u, purge=True)
for g in user_group_list()["groups"]: for g in user_group_list()["groups"]:
if g not in ["all_users", "visitors"]: if g not in ["all_users", "visitors", "admins"]:
user_group_delete(g) user_group_delete(g)
@ -39,9 +38,9 @@ def setup_function(function):
global maindomain global maindomain
maindomain = _get_maindomain() maindomain = _get_maindomain()
user_create("alice", "Alice", "White", maindomain, "test123Ynh") user_create("alice", maindomain, "test123Ynh", admin=True, fullname="Alice White")
user_create("bob", "Bob", "Snow", maindomain, "test123Ynh") user_create("bob", maindomain, "test123Ynh", fullname="Bob Snow")
user_create("jack", "Jack", "Black", maindomain, "test123Ynh") user_create("jack", maindomain, "test123Ynh", fullname="Jack Black")
user_group_create("dev") user_group_create("dev")
user_group_create("apps") user_group_create("apps")
@ -80,6 +79,7 @@ def test_list_groups():
assert "alice" in res assert "alice" in res
assert "bob" in res assert "bob" in res
assert "jack" in res assert "jack" in res
assert "alice" in res["admins"]["members"]
for u in ["alice", "bob", "jack"]: for u in ["alice", "bob", "jack"]:
assert u in res assert u in res
assert u in res[u]["members"] assert u in res[u]["members"]
@ -94,7 +94,7 @@ def test_list_groups():
def test_create_user(mocker): def test_create_user(mocker):
with message(mocker, "user_created"): with message(mocker, "user_created"):
user_create("albert", "Albert", "Good", maindomain, "test123Ynh") user_create("albert", maindomain, "test123Ynh", fullname="Albert Good")
group_res = user_group_list()["groups"] group_res = user_group_list()["groups"]
assert "albert" in user_list()["users"] assert "albert" in user_list()["users"]
@ -175,10 +175,9 @@ def test_import_user(mocker):
def test_export_user(mocker): def test_export_user(mocker):
result = user_export() result = user_export()
aliases = ",".join([alias + maindomain for alias in FIRST_ALIASES])
should_be = ( should_be = (
"username;firstname;lastname;password;mail;mail-alias;mail-forward;mailbox-quota;groups\r\n" "username;firstname;lastname;password;mail;mail-alias;mail-forward;mailbox-quota;groups\r\n"
f"alice;Alice;White;;alice@{maindomain};{aliases};;0;dev\r\n" f"alice;Alice;White;;alice@{maindomain};;;0;admins,dev\r\n"
f"bob;Bob;Snow;;bob@{maindomain};;;0;apps\r\n" f"bob;Bob;Snow;;bob@{maindomain};;;0;apps\r\n"
f"jack;Jack;Black;;jack@{maindomain};;;0;" f"jack;Jack;Black;;jack@{maindomain};;;0;"
) )
@ -212,17 +211,17 @@ def test_del_group(mocker):
def test_create_user_with_password_too_simple(mocker): def test_create_user_with_password_too_simple(mocker):
with raiseYunohostError(mocker, "password_listed"): with raiseYunohostError(mocker, "password_listed"):
user_create("other", "Alice", "White", maindomain, "12") user_create("other", maindomain, "12", fullname="Alice White")
def test_create_user_already_exists(mocker): def test_create_user_already_exists(mocker):
with raiseYunohostError(mocker, "user_already_exists"): with raiseYunohostError(mocker, "user_already_exists"):
user_create("alice", "Alice", "White", maindomain, "test123Ynh") user_create("alice", maindomain, "test123Ynh", fullname="Alice White")
def test_create_user_with_domain_that_doesnt_exists(mocker): def test_create_user_with_domain_that_doesnt_exists(mocker):
with raiseYunohostError(mocker, "domain_unknown"): with raiseYunohostError(mocker, "domain_unknown"):
user_create("alice", "Alice", "White", "doesnt.exists", "test123Ynh") user_create("alice", "doesnt.exists", "test123Ynh", fullname="Alice White")
def test_update_user_with_mail_address_already_taken(mocker): def test_update_user_with_mail_address_already_taken(mocker):
@ -256,7 +255,6 @@ def test_del_group_all_users(mocker):
with raiseYunohostError(mocker, "group_cannot_be_deleted"): with raiseYunohostError(mocker, "group_cannot_be_deleted"):
user_group_delete("all_users") user_group_delete("all_users")
def test_del_group_that_does_not_exist(mocker): def test_del_group_that_does_not_exist(mocker):
with raiseYunohostError(mocker, "group_unknown"): with raiseYunohostError(mocker, "group_unknown"):
user_group_delete("doesnt_exist") user_group_delete("doesnt_exist")
@ -272,8 +270,13 @@ def test_update_user(mocker):
user_update("alice", firstname="NewName", lastname="NewLast") user_update("alice", firstname="NewName", lastname="NewLast")
info = user_info("alice") info = user_info("alice")
assert info["firstname"] == "NewName" assert info["fullname"] == "NewName NewLast"
assert info["lastname"] == "NewLast"
with message(mocker, "user_updated"):
user_update("alice", fullname="New2Name New2Last")
info = user_info("alice")
assert info["fullname"] == "New2Name New2Last"
def test_update_group_add_user(mocker): def test_update_group_add_user(mocker):

View file

@ -1,28 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2013 YunoHost #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
""" yunohost_tools.py
Specific tools
"""
import re import re
import os import os
import subprocess import subprocess
@ -34,7 +27,7 @@ from typing import List
from moulinette import Moulinette, m18n from moulinette import Moulinette, m18n
from moulinette.utils.log import getActionLogger from moulinette.utils.log import getActionLogger
from moulinette.utils.process import call_async_output from moulinette.utils.process import call_async_output
from moulinette.utils.filesystem import read_yaml, write_to_yaml, cp, mkdir, rm from moulinette.utils.filesystem import read_yaml, write_to_yaml, cp, mkdir, rm, chown
from yunohost.app import app_upgrade, app_list from yunohost.app import app_upgrade, app_list
from yunohost.app_catalog import ( from yunohost.app_catalog import (
@ -45,10 +38,12 @@ from yunohost.domain import domain_add
from yunohost.firewall import firewall_upnp from yunohost.firewall import firewall_upnp
from yunohost.service import service_start, service_enable from yunohost.service import service_start, service_enable
from yunohost.regenconf import regen_conf from yunohost.regenconf import regen_conf
from yunohost.utils.packages import ( from yunohost.utils.system import (
_dump_sources_list, _dump_sources_list,
_list_upgradable_apt_packages, _list_upgradable_apt_packages,
ynh_packages_version, ynh_packages_version,
dpkg_is_broken,
dpkg_lock_available,
) )
from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.error import YunohostError, YunohostValidationError
from yunohost.log import is_unit_operation, OperationLogger from yunohost.log import is_unit_operation, OperationLogger
@ -62,14 +57,8 @@ def tools_versions():
return ynh_packages_version() return ynh_packages_version()
def tools_adminpw(new_password, check_strength=True): def tools_rootpw(new_password, check_strength=True):
"""
Change admin password
Keyword argument:
new_password
"""
from yunohost.user import _hash_user_password from yunohost.user import _hash_user_password
from yunohost.utils.password import ( from yunohost.utils.password import (
assert_password_is_strong_enough, assert_password_is_strong_enough,
@ -77,48 +66,33 @@ def tools_adminpw(new_password, check_strength=True):
) )
import spwd import spwd
assert_password_is_compatible(new_password)
if check_strength: if check_strength:
assert_password_is_strong_enough("admin", new_password) assert_password_is_strong_enough("admin", new_password)
assert_password_is_compatible(new_password)
new_hash = _hash_user_password(new_password) new_hash = _hash_user_password(new_password)
from yunohost.utils.ldap import _get_ldap_interface # Write as root password
ldap = _get_ldap_interface()
try: try:
ldap.update( hash_root = spwd.getspnam("root").sp_pwd
"cn=admin",
{"userPassword": [new_hash]},
)
except Exception as e:
logger.error(f"unable to change admin password : {e}")
raise YunohostError("admin_password_change_failed")
else:
# Write as root password
try:
hash_root = spwd.getspnam("root").sp_pwd
with open("/etc/shadow", "r") as before_file: with open("/etc/shadow", "r") as before_file:
before = before_file.read() before = before_file.read()
with open("/etc/shadow", "w") as after_file: with open("/etc/shadow", "w") as after_file:
after_file.write( after_file.write(
before.replace( before.replace(
"root:" + hash_root, "root:" + new_hash.replace("{CRYPT}", "") "root:" + hash_root, "root:" + new_hash.replace("{CRYPT}", "")
)
) )
# An IOError may be thrown if for some reason we can't read/write /etc/passwd )
# A KeyError could also be thrown if 'root' is not in /etc/passwd in the first place (for example because no password defined ?) # An IOError may be thrown if for some reason we can't read/write /etc/passwd
# (c.f. the line about getspnam) # A KeyError could also be thrown if 'root' is not in /etc/passwd in the first place (for example because no password defined ?)
except (IOError, KeyError): # (c.f. the line about getspnam)
logger.warning(m18n.n("root_password_desynchronized")) except (IOError, KeyError):
return logger.warning(m18n.n("root_password_desynchronized"))
return
logger.info(m18n.n("root_password_replaced_by_admin_password")) else:
logger.success(m18n.n("admin_password_changed")) logger.info(m18n.n("root_password_changed"))
def tools_maindomain(new_main_domain=None): def tools_maindomain(new_main_domain=None):
@ -166,39 +140,17 @@ def _set_hostname(hostname, pretty_hostname=None):
logger.debug(out) logger.debug(out)
def _detect_virt():
"""
Returns the output of systemd-detect-virt (so e.g. 'none' or 'lxc' or ...)
You can check the man of the command to have a list of possible outputs...
"""
p = subprocess.Popen(
"systemd-detect-virt".split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
out, _ = p.communicate()
return out.split()[0]
@is_unit_operation() @is_unit_operation()
def tools_postinstall( def tools_postinstall(
operation_logger, operation_logger,
domain, domain,
username,
fullname,
password, password,
ignore_dyndns=False, ignore_dyndns=False,
force_password=False,
force_diskspace=False, force_diskspace=False,
): ):
"""
YunoHost post-install
Keyword argument:
domain -- YunoHost main domain
ignore_dyndns -- Do not subscribe domain to a DynDNS service (only
needed for nohost.me, noho.st domains)
password -- YunoHost admin password
"""
from yunohost.dyndns import _dyndns_available from yunohost.dyndns import _dyndns_available
from yunohost.utils.dns import is_yunohost_dyndns_domain from yunohost.utils.dns import is_yunohost_dyndns_domain
from yunohost.utils.password import ( from yunohost.utils.password import (
@ -206,6 +158,7 @@ def tools_postinstall(
assert_password_is_compatible, assert_password_is_compatible,
) )
from yunohost.domain import domain_main_domain from yunohost.domain import domain_main_domain
from yunohost.user import user_create
import psutil import psutil
# Do some checks at first # Do some checks at first
@ -232,9 +185,7 @@ def tools_postinstall(
# Check password # Check password
assert_password_is_compatible(password) assert_password_is_compatible(password)
assert_password_is_strong_enough("admin", password)
if not force_password:
assert_password_is_strong_enough("admin", password)
# If this is a nohost.me/noho.st, actually check for availability # If this is a nohost.me/noho.st, actually check for availability
if not ignore_dyndns and is_yunohost_dyndns_domain(domain): if not ignore_dyndns and is_yunohost_dyndns_domain(domain):
@ -273,8 +224,10 @@ def tools_postinstall(
domain_add(domain, dyndns) domain_add(domain, dyndns)
domain_main_domain(domain) domain_main_domain(domain)
user_create(username, domain, password, admin=True, fullname=fullname)
# Update LDAP admin and create home dir # Update LDAP admin and create home dir
tools_adminpw(password, check_strength=not force_password) tools_rootpw(password)
# Enable UPnP silently and reload firewall # Enable UPnP silently and reload firewall
firewall_upnp("enable", no_refresh=True) firewall_upnp("enable", no_refresh=True)
@ -435,13 +388,12 @@ def tools_upgrade(operation_logger, target=None):
apps -- List of apps to upgrade (or [] to update all apps) apps -- List of apps to upgrade (or [] to update all apps)
system -- True to upgrade system system -- True to upgrade system
""" """
from yunohost.utils import packages
if packages.dpkg_is_broken(): if dpkg_is_broken():
raise YunohostValidationError("dpkg_is_broken") raise YunohostValidationError("dpkg_is_broken")
# Check for obvious conflict with other dpkg/apt commands already running in parallel # Check for obvious conflict with other dpkg/apt commands already running in parallel
if not packages.dpkg_lock_available(): if not dpkg_lock_available():
raise YunohostValidationError("dpkg_lock_not_available") raise YunohostValidationError("dpkg_lock_not_available")
if target not in ["apps", "system"]: if target not in ["apps", "system"]:
@ -1012,7 +964,7 @@ class Migration:
def description(self): def description(self):
return m18n.n(f"migration_description_{self.id}") return m18n.n(f"migration_description_{self.id}")
def ldap_migration(self, run): def ldap_migration(run):
def func(self): def func(self):
# Backup LDAP before the migration # Backup LDAP before the migration
@ -1040,22 +992,28 @@ class Migration:
try: try:
run(self, backup_folder) run(self, backup_folder)
except Exception: except Exception:
logger.warning( if self.ldap_migration_started:
m18n.n("migration_ldap_migration_failed_trying_to_rollback") logger.warning(
) m18n.n("migration_ldap_migration_failed_trying_to_rollback")
os.system("systemctl stop slapd") )
# To be sure that we don't keep some part of the old config os.system("systemctl stop slapd")
rm("/etc/ldap/slapd.d", force=True, recursive=True) # To be sure that we don't keep some part of the old config
cp(f"{backup_folder}/ldap_config", "/etc/ldap", recursive=True) rm("/etc/ldap", force=True, recursive=True)
cp(f"{backup_folder}/ldap_db", "/var/lib/ldap", recursive=True) cp(f"{backup_folder}/ldap_config", "/etc/ldap", recursive=True)
cp( chown("/etc/ldap/schema/", "openldap", "openldap", recursive=True)
f"{backup_folder}/apps_settings", chown("/etc/ldap/slapd.d/", "openldap", "openldap", recursive=True)
"/etc/yunohost/apps", rm("/var/lib/ldap", force=True, recursive=True)
recursive=True, cp(f"{backup_folder}/ldap_db", "/var/lib/ldap", recursive=True)
) rm("/etc/yunohost/apps", force=True, recursive=True)
os.system("systemctl start slapd") chown("/var/lib/ldap/", "openldap", recursive=True)
rm(backup_folder, force=True, recursive=True) cp(
logger.info(m18n.n("migration_ldap_rollback_success")) f"{backup_folder}/apps_settings",
"/etc/yunohost/apps",
recursive=True,
)
os.system("systemctl start slapd")
rm(backup_folder, force=True, recursive=True)
logger.info(m18n.n("migration_ldap_rollback_success"))
raise raise
else: else:
rm(backup_folder, force=True, recursive=True) rm(backup_folder, force=True, recursive=True)

View file

@ -1,28 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2014 YUNOHOST.ORG #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
""" yunohost_user.py
Manage users
"""
import os import os
import re import re
import pwd import pwd
@ -40,6 +33,7 @@ from moulinette.utils.process import check_output
from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.utils.error import YunohostError, YunohostValidationError
from yunohost.service import service_status from yunohost.service import service_status
from yunohost.log import is_unit_operation from yunohost.log import is_unit_operation
from yunohost.utils.system import binary_to_human
logger = getActionLogger("yunohost.user") logger = getActionLogger("yunohost.user")
@ -55,7 +49,7 @@ FIELDS_FOR_IMPORT = {
"groups": r"^|([a-z0-9_]+(,?[a-z0-9_]+)*)$", "groups": r"^|([a-z0-9_]+(,?[a-z0-9_]+)*)$",
} }
FIRST_ALIASES = ["root@", "admin@", "webmaster@", "postmaster@", "abuse@"] ADMIN_ALIASES = ["root@", "admin@", "admins", "webmaster@", "postmaster@", "abuse@"]
def user_list(fields=None): def user_list(fields=None):
@ -133,14 +127,29 @@ def user_list(fields=None):
def user_create( def user_create(
operation_logger, operation_logger,
username, username,
firstname,
lastname,
domain, domain,
password, password,
fullname=None,
firstname=None,
lastname=None,
mailbox_quota="0", mailbox_quota="0",
admin=False,
from_import=False, from_import=False,
): ):
if firstname or lastname:
logger.warning("Options --firstname / --lastname of 'yunohost user create' are deprecated. We recommend using --fullname instead.")
if not fullname or not fullname.strip():
if not firstname.strip():
raise YunohostValidationError("You should specify the fullname of the user using option -F")
lastname = lastname or " " # Stupid hack because LDAP requires the sn/lastname attr, but it accepts a single whitespace...
fullname = f"{firstname} {lastname}".strip()
else:
fullname = fullname.strip()
firstname = fullname.split()[0]
lastname = ' '.join(fullname.split()[1:]) or " " # Stupid hack because LDAP requires the sn/lastname attr, but it accepts a single whitespace...
from yunohost.domain import domain_list, _get_maindomain, _assert_domain_exists from yunohost.domain import domain_list, _get_maindomain, _assert_domain_exists
from yunohost.hook import hook_callback from yunohost.hook import hook_callback
from yunohost.utils.password import ( from yunohost.utils.password import (
@ -151,7 +160,7 @@ def user_create(
# Ensure compatibility and sufficiently complex password # Ensure compatibility and sufficiently complex password
assert_password_is_compatible(password) assert_password_is_compatible(password)
assert_password_is_strong_enough("user", password) assert_password_is_strong_enough("admin" if admin else "user", password)
# Validate domain used for email address/xmpp account # Validate domain used for email address/xmpp account
if domain is None: if domain is None:
@ -193,9 +202,10 @@ def user_create(
raise YunohostValidationError("system_username_exists") raise YunohostValidationError("system_username_exists")
main_domain = _get_maindomain() main_domain = _get_maindomain()
aliases = [alias + main_domain for alias in FIRST_ALIASES] # FIXME: should forbit root@any.domain, not just main domain?
admin_aliases = [alias + main_domain for alias in ADMIN_ALIASES]
if mail in aliases: if mail in admin_aliases:
raise YunohostValidationError("mail_unavailable") raise YunohostValidationError("mail_unavailable")
if not from_import: if not from_import:
@ -205,15 +215,17 @@ def user_create(
all_uid = {str(x.pw_uid) for x in pwd.getpwall()} all_uid = {str(x.pw_uid) for x in pwd.getpwall()}
all_gid = {str(x.gr_gid) for x in grp.getgrall()} all_gid = {str(x.gr_gid) for x in grp.getgrall()}
# Prevent users from obtaining uid 1007 which is the uid of the legacy admin,
# and there could be a edge case where a new user becomes owner of an old, removed admin user
all_uid.add("1007")
all_gid.add("1007")
uid_guid_found = False uid_guid_found = False
while not uid_guid_found: while not uid_guid_found:
# LXC uid number is limited to 65536 by default # LXC uid number is limited to 65536 by default
uid = str(random.randint(1001, 65000)) uid = str(random.randint(1001, 65000))
uid_guid_found = uid not in all_uid and uid not in all_gid uid_guid_found = uid not in all_uid and uid not in all_gid
# Adapt values for LDAP
fullname = f"{firstname} {lastname}"
attr_dict = { attr_dict = {
"objectClass": [ "objectClass": [
"mailAccount", "mailAccount",
@ -236,10 +248,6 @@ def user_create(
"loginShell": ["/bin/bash"], "loginShell": ["/bin/bash"],
} }
# If it is the first user, add some aliases
if not ldap.search(base="ou=users", filter="uid=*"):
attr_dict["mail"] = [attr_dict["mail"]] + aliases
try: try:
ldap.add(f"uid={username},ou=users", attr_dict) ldap.add(f"uid={username},ou=users", attr_dict)
except Exception as e: except Exception as e:
@ -265,6 +273,8 @@ def user_create(
# Create group for user and add to group 'all_users' # Create group for user and add to group 'all_users'
user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False) user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False)
user_group_update(groupname="all_users", add=username, force=True, sync_perm=True) user_group_update(groupname="all_users", add=username, force=True, sync_perm=True)
if admin:
user_group_update(groupname="admins", add=username, sync_perm=True)
# Trigger post_user_create hooks # Trigger post_user_create hooks
env_dict = { env_dict = {
@ -286,14 +296,7 @@ def user_create(
@is_unit_operation([("username", "user")]) @is_unit_operation([("username", "user")])
def user_delete(operation_logger, username, purge=False, from_import=False): def user_delete(operation_logger, username, purge=False, from_import=False):
"""
Delete user
Keyword argument:
username -- Username to delete
purge
"""
from yunohost.hook import hook_callback from yunohost.hook import hook_callback
from yunohost.utils.ldap import _get_ldap_interface from yunohost.utils.ldap import _get_ldap_interface
@ -351,22 +354,17 @@ def user_update(
remove_mailalias=None, remove_mailalias=None,
mailbox_quota=None, mailbox_quota=None,
from_import=False, from_import=False,
fullname=None,
): ):
"""
Update user informations
Keyword argument: if firstname or lastname:
lastname logger.warning("Options --firstname / --lastname of 'yunohost user create' are deprecated. We recommend using --fullname instead.")
mail
firstname if fullname and fullname.strip():
add_mailalias -- Mail aliases to add fullname = fullname.strip()
remove_mailforward -- Mailforward addresses to remove firstname = fullname.split()[0]
username -- Username of user to update lastname = ' '.join(fullname.split()[1:]) or " " # Stupid hack because LDAP requires the sn/lastname attr, but it accepts a single whitespace...
add_mailforward -- Mailforward addresses to add
change_password -- New password to set
remove_mailalias -- Mail aliases to remove
"""
from yunohost.domain import domain_list, _get_maindomain from yunohost.domain import domain_list, _get_maindomain
from yunohost.app import app_ssowatconf from yunohost.app import app_ssowatconf
from yunohost.utils.password import ( from yunohost.utils.password import (
@ -380,7 +378,7 @@ def user_update(
# Populate user informations # Populate user informations
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
attrs_to_fetch = ["givenName", "sn", "mail", "maildrop"] attrs_to_fetch = ["givenName", "sn", "mail", "maildrop", "memberOf"]
result = ldap.search( result = ldap.search(
base="ou=users", base="ou=users",
filter="uid=" + username, filter="uid=" + username,
@ -396,20 +394,20 @@ def user_update(
if firstname: if firstname:
new_attr_dict["givenName"] = [firstname] # TODO: Validate new_attr_dict["givenName"] = [firstname] # TODO: Validate
new_attr_dict["cn"] = new_attr_dict["displayName"] = [ new_attr_dict["cn"] = new_attr_dict["displayName"] = [
firstname + " " + user["sn"][0] (firstname + " " + user["sn"][0]).strip()
] ]
env_dict["YNH_USER_FIRSTNAME"] = firstname env_dict["YNH_USER_FIRSTNAME"] = firstname
if lastname: if lastname:
new_attr_dict["sn"] = [lastname] # TODO: Validate new_attr_dict["sn"] = [lastname] # TODO: Validate
new_attr_dict["cn"] = new_attr_dict["displayName"] = [ new_attr_dict["cn"] = new_attr_dict["displayName"] = [
user["givenName"][0] + " " + lastname (user["givenName"][0] + " " + lastname).strip()
] ]
env_dict["YNH_USER_LASTNAME"] = lastname env_dict["YNH_USER_LASTNAME"] = lastname
if lastname and firstname: if lastname and firstname:
new_attr_dict["cn"] = new_attr_dict["displayName"] = [ new_attr_dict["cn"] = new_attr_dict["displayName"] = [
firstname + " " + lastname (firstname + " " + lastname).strip()
] ]
# change_password is None if user_update is not called to change the password # change_password is None if user_update is not called to change the password
@ -421,16 +419,17 @@ def user_update(
change_password = Moulinette.prompt( change_password = Moulinette.prompt(
m18n.n("ask_password"), is_password=True, confirm=True m18n.n("ask_password"), is_password=True, confirm=True
) )
# Ensure compatibility and sufficiently complex password # Ensure compatibility and sufficiently complex password
assert_password_is_compatible(change_password) assert_password_is_compatible(change_password)
assert_password_is_strong_enough("user", change_password) is_admin = "cn=admins,ou=groups,dc=yunohost,dc=org" in user["memberOf"]
assert_password_is_strong_enough("admin" if is_admin else "user", change_password)
new_attr_dict["userPassword"] = [_hash_user_password(change_password)] new_attr_dict["userPassword"] = [_hash_user_password(change_password)]
env_dict["YNH_USER_PASSWORD"] = change_password env_dict["YNH_USER_PASSWORD"] = change_password
if mail: if mail:
main_domain = _get_maindomain() main_domain = _get_maindomain()
aliases = [alias + main_domain for alias in FIRST_ALIASES]
# If the requested mail address is already as main address or as an alias by this user # If the requested mail address is already as main address or as an alias by this user
if mail in user["mail"]: if mail in user["mail"]:
@ -445,6 +444,9 @@ def user_update(
raise YunohostError( raise YunohostError(
"mail_domain_unknown", domain=mail[mail.find("@") + 1 :] "mail_domain_unknown", domain=mail[mail.find("@") + 1 :]
) )
# FIXME: should also forbid root@any.domain and not just the main domain
aliases = [alias + main_domain for alias in ADMIN_ALIASES]
if mail in aliases: if mail in aliases:
raise YunohostValidationError("mail_unavailable") raise YunohostValidationError("mail_unavailable")
@ -537,7 +539,7 @@ def user_info(username):
ldap = _get_ldap_interface() ldap = _get_ldap_interface()
user_attrs = ["cn", "mail", "uid", "maildrop", "givenName", "sn", "mailuserquota"] user_attrs = ["cn", "mail", "uid", "maildrop", "mailuserquota"]
if len(username.split("@")) == 2: if len(username.split("@")) == 2:
filter = "mail=" + username filter = "mail=" + username
@ -554,8 +556,6 @@ def user_info(username):
result_dict = { result_dict = {
"username": user["uid"][0], "username": user["uid"][0],
"fullname": user["cn"][0], "fullname": user["cn"][0],
"firstname": user["givenName"][0],
"lastname": user["sn"][0],
"mail": user["mail"][0], "mail": user["mail"][0],
"mail-aliases": [], "mail-aliases": [],
"mail-forward": [], "mail-forward": [],
@ -596,7 +596,7 @@ def user_info(username):
if has_value: if has_value:
storage_use = int(has_value.group(1)) storage_use = int(has_value.group(1))
storage_use = _convertSize(storage_use) storage_use = binary_to_human(storage_use)
if is_limited: if is_limited:
has_percent = re.search(r"%=(\d+)", cmd_result) has_percent = re.search(r"%=(\d+)", cmd_result)
@ -849,10 +849,9 @@ def user_import(operation_logger, csvfile, update=False, delete=False):
user_update( user_update(
new_infos["username"], new_infos["username"],
new_infos["firstname"], firstname=new_infos["firstname"],
new_infos["lastname"], lastname=new_infos["lastname"],
new_infos["mail"], change_password=new_infos["password"],
new_infos["password"],
mailbox_quota=new_infos["mailbox-quota"], mailbox_quota=new_infos["mailbox-quota"],
mail=new_infos["mail"], mail=new_infos["mail"],
add_mailalias=new_infos["mail-alias"], add_mailalias=new_infos["mail-alias"],
@ -892,12 +891,12 @@ def user_import(operation_logger, csvfile, update=False, delete=False):
try: try:
user_create( user_create(
user["username"], user["username"],
user["firstname"],
user["lastname"],
user["domain"], user["domain"],
user["password"], user["password"],
user["mailbox-quota"], user["mailbox-quota"],
from_import=True, from_import=True,
firstname=user["firstname"],
lastname=user["lastname"],
) )
update(user) update(user)
result["created"] += 1 result["created"] += 1
@ -1070,7 +1069,7 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True):
# #
# We also can't delete "all_users" because that's a special group... # We also can't delete "all_users" because that's a special group...
existing_users = list(user_list()["users"].keys()) existing_users = list(user_list()["users"].keys())
undeletable_groups = existing_users + ["all_users", "visitors"] undeletable_groups = existing_users + ["all_users", "visitors", "admins"]
if groupname in undeletable_groups and not force: if groupname in undeletable_groups and not force:
raise YunohostValidationError("group_cannot_be_deleted", group=groupname) raise YunohostValidationError("group_cannot_be_deleted", group=groupname)
@ -1323,15 +1322,6 @@ def user_ssh_remove_key(username, key):
# End SSH subcategory # End SSH subcategory
# #
def _convertSize(num, suffix=""):
for unit in ["K", "M", "G", "T", "P", "E", "Z"]:
if abs(num) < 1024.0:
return "{:3.1f}{}{}".format(num, unit, suffix)
num /= 1024.0
return "{:.1f}{}{}".format(num, "Yi", suffix)
def _hash_user_password(password): def _hash_user_password(password):
""" """
This function computes and return a salted hash for the password in input. This function computes and return a salted hash for the password in input.

View file

@ -0,0 +1,18 @@
#
# Copyright (c) 2022 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#

View file

@ -1,24 +1,21 @@
# -*- coding: utf-8 -*- #
# Copyright (c) 2022 YunoHost Contributors
""" License #
# This file is part of YunoHost (see https://yunohost.org)
Copyright (C) 2018 YUNOHOST.ORG #
# This program is free software: you can redistribute it and/or modify
This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as
it under the terms of the GNU Affero General Public License as published # published by the Free Software Foundation, either version 3 of the
by the Free Software Foundation, either version 3 of the License, or # License, or (at your option) any later version.
(at your option) any later version. #
# This program is distributed in the hope that it will be useful,
This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of
but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details.
GNU Affero General Public License for more details. #
# You should have received a copy of the GNU Affero General Public License
You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program; if not, see http://www.gnu.org/licenses #
"""
import glob import glob
import os import os
import re import re
@ -49,6 +46,7 @@ from yunohost.log import OperationLogger
logger = getActionLogger("yunohost.config") logger = getActionLogger("yunohost.config")
CONFIG_PANEL_VERSION_SUPPORTED = 1.0 CONFIG_PANEL_VERSION_SUPPORTED = 1.0
# Those js-like evaluate functions are used to eval safely visible attributes # Those js-like evaluate functions are used to eval safely visible attributes
# The goal is to evaluate in the same way than js simple-evaluate # The goal is to evaluate in the same way than js simple-evaluate
# https://github.com/shepherdwind/simple-evaluate # https://github.com/shepherdwind/simple-evaluate
@ -273,6 +271,10 @@ class ConfigPanel:
logger.debug(f"Formating result in '{mode}' mode") logger.debug(f"Formating result in '{mode}' mode")
result = {} result = {}
for panel, section, option in self._iterate(): for panel, section, option in self._iterate():
if section["is_action_section"] and mode != "full":
continue
key = f"{panel['id']}.{section['id']}.{option['id']}" key = f"{panel['id']}.{section['id']}.{option['id']}"
if mode == "export": if mode == "export":
result[option["id"]] = option.get("current_value") result[option["id"]] = option.get("current_value")
@ -311,6 +313,82 @@ class ConfigPanel:
else: else:
return result return result
def list_actions(self):
actions = {}
# FIXME : meh, loading the entire config panel is again going to cause
# stupid issues for domain (e.g loading registrar stuff when willing to just list available actions ...)
self.filter_key = ""
self._get_config_panel()
for panel, section, option in self._iterate():
if option["type"] == "button":
key = f"{panel['id']}.{section['id']}.{option['id']}"
actions[key] = _value_for_locale(option["ask"])
return actions
def run_action(
self, action=None, args=None, args_file=None, operation_logger=None
):
#
# FIXME : this stuff looks a lot like set() ...
#
self.filter_key = ".".join(action.split(".")[:2])
action_id = action.split(".")[2]
# Read config panel toml
self._get_config_panel()
# FIXME: should also check that there's indeed a key called action
if not self.config:
raise YunohostValidationError(f"No action named {action}", raw_msg=True)
# Import and parse pre-answered options
logger.debug("Import and parse pre-answered options")
self._parse_pre_answered(args, None, args_file)
# Read or get values and hydrate the config
self._load_current_values()
self._hydrate()
Question.operation_logger = operation_logger
self._ask(action=action_id)
# FIXME: here, we could want to check constrains on
# the action's visibility / requirements wrt to the answer to questions ...
if operation_logger:
operation_logger.start()
try:
self._run_action(action_id)
except YunohostError:
raise
# Script got manually interrupted ...
# N.B. : KeyboardInterrupt does not inherit from Exception
except (KeyboardInterrupt, EOFError):
error = m18n.n("operation_interrupted")
logger.error(m18n.n("config_action_failed", action=action, error=error))
raise
# Something wrong happened in Yunohost's code (most probably hook_exec)
except Exception:
import traceback
error = m18n.n("unexpected_error", error="\n" + traceback.format_exc())
logger.error(m18n.n("config_action_failed", action=action, error=error))
raise
finally:
# Delete files uploaded from API
# FIXME : this is currently done in the context of config panels,
# but could also happen in the context of app install ... (or anywhere else
# where we may parse args etc...)
FileQuestion.clean_upload_dirs()
# FIXME: i18n
logger.success(f"Action {action_id} successful")
operation_logger.success()
def set( def set(
self, key=None, value=None, args=None, args_file=None, operation_logger=None self, key=None, value=None, args=None, args_file=None, operation_logger=None
): ):
@ -417,6 +495,7 @@ class ConfigPanel:
"name": "", "name": "",
"services": [], "services": [],
"optional": True, "optional": True,
"is_action_section": False,
}, },
}, },
"options": { "options": {
@ -443,6 +522,9 @@ class ConfigPanel:
"accept", "accept",
"redact", "redact",
"filter", "filter",
"readonly",
"enabled",
# "confirm", # TODO: to ask confirmation before running an action
], ],
"defaults": {}, "defaults": {},
}, },
@ -485,6 +567,9 @@ class ConfigPanel:
elif level == "sections": elif level == "sections":
subnode["name"] = key # legacy subnode["name"] = key # legacy
subnode.setdefault("optional", raw_infos.get("optional", True)) subnode.setdefault("optional", raw_infos.get("optional", True))
# If this section contains at least one button, it becomes an "action" section
if subnode["type"] == "button":
out["is_action_section"] = True
out.setdefault(sublevel, []).append(subnode) out.setdefault(sublevel, []).append(subnode)
# Key/value are a property # Key/value are a property
else: else:
@ -525,18 +610,39 @@ class ConfigPanel:
"max_progression", "max_progression",
] ]
forbidden_keywords += format_description["sections"] forbidden_keywords += format_description["sections"]
forbidden_readonly_types = [
"password",
"app",
"domain",
"user",
"file"
]
for _, _, option in self._iterate(): for _, _, option in self._iterate():
if option["id"] in forbidden_keywords: if option["id"] in forbidden_keywords:
raise YunohostError("config_forbidden_keyword", keyword=option["id"]) raise YunohostError("config_forbidden_keyword", keyword=option["id"])
if (
option.get("readonly", False) and
option.get("type", "string") in forbidden_readonly_types
):
raise YunohostError(
"config_forbidden_readonly_type",
type=option["type"],
id=option["id"]
)
return self.config return self.config
def _hydrate(self): def _hydrate(self):
# Hydrating config panel with current value # Hydrating config panel with current value
for _, _, option in self._iterate(): for _, section, option in self._iterate():
if option["id"] not in self.values: if option["id"] not in self.values:
allowed_empty_types = ["alert", "display_text", "markdown", "file"]
if ( allowed_empty_types = ["alert", "display_text", "markdown", "file", "button"]
if section["is_action_section"] and option.get("default") is not None:
self.values[option["id"]] = option["default"]
elif (
option["type"] in allowed_empty_types option["type"] in allowed_empty_types
or option.get("bind") == "null" or option.get("bind") == "null"
): ):
@ -554,13 +660,16 @@ class ConfigPanel:
return self.values return self.values
def _ask(self): def _ask(self, action=None):
logger.debug("Ask unanswered question and prevalidate data") logger.debug("Ask unanswered question and prevalidate data")
if "i18n" in self.config: if "i18n" in self.config:
for panel, section, option in self._iterate(): for panel, section, option in self._iterate():
if "ask" not in option: if "ask" not in option:
option["ask"] = m18n.n(self.config["i18n"] + "_" + option["id"]) option["ask"] = m18n.n(self.config["i18n"] + "_" + option["id"])
# auto add i18n help text if present in locales
if m18n.key_exists(self.config["i18n"] + "_" + option["id"] + '_help'):
option["help"] = m18n.n(self.config["i18n"] + "_" + option["id"] + '_help')
def display_header(message): def display_header(message):
"""CLI panel/section header display""" """CLI panel/section header display"""
@ -568,20 +677,40 @@ class ConfigPanel:
Moulinette.display(colorize(message, "purple")) Moulinette.display(colorize(message, "purple"))
for panel, section, obj in self._iterate(["panel", "section"]): for panel, section, obj in self._iterate(["panel", "section"]):
if panel == obj:
name = _value_for_locale(panel["name"]) if section and section.get("visible") and not evaluate_simple_js_expression(
display_header(f"\n{'='*40}\n>>>> {name}\n{'='*40}") section["visible"], context=self.new_values
):
continue
# Ugly hack to skip action section ... except when when explicitly running actions
if not action:
if section and section["is_action_section"]:
continue
if panel == obj:
name = _value_for_locale(panel["name"])
display_header(f"\n{'='*40}\n>>>> {name}\n{'='*40}")
else:
name = _value_for_locale(section["name"])
if name:
display_header(f"\n# {name}")
elif section:
# filter action section options in case of multiple buttons
section["options"] = [
option for option in section["options"]
if option.get("type", "string") != "button" or option["id"] == action
]
if panel == obj:
continue continue
name = _value_for_locale(section["name"])
if name:
display_header(f"\n# {name}")
# Check and ask unanswered questions # Check and ask unanswered questions
prefilled_answers = self.args.copy() prefilled_answers = self.args.copy()
prefilled_answers.update(self.new_values) prefilled_answers.update(self.new_values)
questions = ask_questions_and_parse_answers( questions = ask_questions_and_parse_answers(
section["options"], {question["name"]: question for question in section["options"]},
prefilled_answers=prefilled_answers, prefilled_answers=prefilled_answers,
current_values=self.values, current_values=self.values,
hooks=self.hooks, hooks=self.hooks,
@ -594,8 +723,6 @@ class ConfigPanel:
} }
) )
self.errors = None
def _get_default_values(self): def _get_default_values(self):
return { return {
option["id"]: option["default"] option["id"]: option["default"]
@ -702,6 +829,7 @@ class Question:
self.default = question.get("default", None) self.default = question.get("default", None)
self.optional = question.get("optional", False) self.optional = question.get("optional", False)
self.visible = question.get("visible", None) self.visible = question.get("visible", None)
self.readonly = question.get("readonly", False)
# Don't restrict choices if there's none specified # Don't restrict choices if there's none specified
self.choices = question.get("choices", None) self.choices = question.get("choices", None)
self.pattern = question.get("pattern", self.pattern) self.pattern = question.get("pattern", self.pattern)
@ -762,8 +890,10 @@ class Question:
# Display question if no value filled or if it's a readonly message # Display question if no value filled or if it's a readonly message
if Moulinette.interface.type == "cli" and os.isatty(1): if Moulinette.interface.type == "cli" and os.isatty(1):
text_for_user_input_in_cli = self._format_text_for_user_input_in_cli() text_for_user_input_in_cli = self._format_text_for_user_input_in_cli()
if getattr(self, "readonly", False): if self.readonly:
Moulinette.display(text_for_user_input_in_cli) Moulinette.display(text_for_user_input_in_cli)
self.value = self.values[self.name] = self.current_value
return self.values
elif self.value is None: elif self.value is None:
self._prompt(text_for_user_input_in_cli) self._prompt(text_for_user_input_in_cli)
@ -823,7 +953,12 @@ class Question:
text_for_user_input_in_cli = _value_for_locale(self.ask) text_for_user_input_in_cli = _value_for_locale(self.ask)
if self.choices: if self.readonly:
text_for_user_input_in_cli = colorize(text_for_user_input_in_cli, "purple")
if self.choices:
return text_for_user_input_in_cli + f" {self.choices[self.current_value]}"
return text_for_user_input_in_cli + f" {self.humanize(self.current_value)}"
elif self.choices:
# Prevent displaying a shitload of choices # Prevent displaying a shitload of choices
# (e.g. 100+ available users when choosing an app admin...) # (e.g. 100+ available users when choosing an app admin...)
@ -909,7 +1044,7 @@ class DateQuestion(StringQuestion):
class TimeQuestion(StringQuestion): class TimeQuestion(StringQuestion):
pattern = { pattern = {
"regexp": r"^(1[12]|0?\d):[0-5]\d$", "regexp": r"^(?:\d|[01]\d|2[0-3]):[0-5]\d$",
"error": "config_validate_time", # i18n: config_validate_time "error": "config_validate_time", # i18n: config_validate_time
} }
@ -923,6 +1058,7 @@ class ColorQuestion(StringQuestion):
class TagsQuestion(Question): class TagsQuestion(Question):
argument_type = "tags" argument_type = "tags"
default_value = ""
@staticmethod @staticmethod
def humanize(value, option={}): def humanize(value, option={}):
@ -1094,7 +1230,8 @@ class BooleanQuestion(Question):
def _format_text_for_user_input_in_cli(self): def _format_text_for_user_input_in_cli(self):
text_for_user_input_in_cli = super()._format_text_for_user_input_in_cli() text_for_user_input_in_cli = super()._format_text_for_user_input_in_cli()
text_for_user_input_in_cli += " [yes | no]" if not self.readonly:
text_for_user_input_in_cli += " [yes | no]"
return text_for_user_input_in_cli return text_for_user_input_in_cli
@ -1184,6 +1321,8 @@ class UserQuestion(Question):
) )
if self.default is None: if self.default is None:
# FIXME: this code is obsolete with the new admins group
# Should be replaced by something like "any first user we find in the admin group"
root_mail = "root@%s" % _get_maindomain() root_mail = "root@%s" % _get_maindomain()
for user in self.choices.keys(): for user in self.choices.keys():
if root_mail in user_info(user).get("mail-aliases", []): if root_mail in user_info(user).get("mail-aliases", []):
@ -1191,6 +1330,29 @@ class UserQuestion(Question):
break break
class GroupQuestion(Question):
argument_type = "group"
def __init__(self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {}):
from yunohost.user import user_group_list
super().__init__(question, context)
self.choices = list(user_group_list(short=True)["groups"])
def _human_readable_group(g):
# i18n: visitors
# i18n: all_users
# i18n: admins
return m18n.n(g) if g in ["visitors", "all_users", "admins"] else g
self.choices = {g:_human_readable_group(g) for g in self.choices}
if self.default is None:
self.default = "all_users"
class NumberQuestion(Question): class NumberQuestion(Question):
argument_type = "number" argument_type = "number"
default_value = None default_value = None
@ -1247,7 +1409,6 @@ class NumberQuestion(Question):
class DisplayTextQuestion(Question): class DisplayTextQuestion(Question):
argument_type = "display_text" argument_type = "display_text"
readonly = True
def __init__( def __init__(
self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {} self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {}
@ -1255,6 +1416,7 @@ class DisplayTextQuestion(Question):
super().__init__(question, context, hooks) super().__init__(question, context, hooks)
self.optional = True self.optional = True
self.readonly = True
self.style = question.get( self.style = question.get(
"style", "info" if question["type"] == "alert" else "" "style", "info" if question["type"] == "alert" else ""
) )
@ -1334,6 +1496,17 @@ class FileQuestion(Question):
return self.value return self.value
class ButtonQuestion(Question):
argument_type = "button"
enabled = None
def __init__(
self, question, context: Mapping[str, Any] = {}, hooks: Dict[str, Callable] = {}
):
super().__init__(question, context, hooks)
self.enabled = question.get("enabled", None)
ARGUMENTS_TYPE_PARSERS = { ARGUMENTS_TYPE_PARSERS = {
"string": StringQuestion, "string": StringQuestion,
"text": StringQuestion, "text": StringQuestion,
@ -1349,6 +1522,7 @@ ARGUMENTS_TYPE_PARSERS = {
"boolean": BooleanQuestion, "boolean": BooleanQuestion,
"domain": DomainQuestion, "domain": DomainQuestion,
"user": UserQuestion, "user": UserQuestion,
"group": GroupQuestion,
"number": NumberQuestion, "number": NumberQuestion,
"range": NumberQuestion, "range": NumberQuestion,
"display_text": DisplayTextQuestion, "display_text": DisplayTextQuestion,
@ -1356,6 +1530,7 @@ ARGUMENTS_TYPE_PARSERS = {
"markdown": DisplayTextQuestion, "markdown": DisplayTextQuestion,
"file": FileQuestion, "file": FileQuestion,
"app": AppQuestion, "app": AppQuestion,
"button": ButtonQuestion,
} }
@ -1393,10 +1568,24 @@ def ask_questions_and_parse_answers(
context = {**current_values, **answers} context = {**current_values, **answers}
out = [] out = []
for raw_question in raw_questions: for name, raw_question in raw_questions.items():
raw_question['name'] = name
question_class = ARGUMENTS_TYPE_PARSERS[raw_question.get("type", "string")] question_class = ARGUMENTS_TYPE_PARSERS[raw_question.get("type", "string")]
raw_question["value"] = answers.get(raw_question["name"]) raw_question["value"] = answers.get(name)
question = question_class(raw_question, context=context, hooks=hooks) question = question_class(raw_question, context=context, hooks=hooks)
if question.type == "button":
if (
question.enabled is None # type: ignore
or evaluate_simple_js_expression(question.enabled, context=context) # type: ignore
):
continue
else:
raise YunohostValidationError(
"config_action_disabled",
action=question.name,
help=_value_for_locale(question.help)
)
new_values = question.ask_if_needed() new_values = question.ask_if_needed()
answers.update(new_values) answers.update(new_values)
context.update(new_values) context.update(new_values)

Some files were not shown because too many files have changed in this diff Show more