mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge branch 'stretch-unstable' into remove-deprecated-helpers
This commit is contained in:
commit
9a95004efc
87 changed files with 6865 additions and 4324 deletions
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
custom: https://donate.yunohost.org
|
||||
liberapay: YunoHost
|
19
.travis.yml
19
.travis.yml
|
@ -1,5 +1,16 @@
|
|||
language: python
|
||||
install: "pip install pytest pyyaml"
|
||||
python:
|
||||
- "2.7"
|
||||
script: "py.test tests"
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: TOXENV=lint
|
||||
include:
|
||||
- python: 2.7
|
||||
env: TOXENV=py27
|
||||
- python: 2.7
|
||||
env: TOXENV=lint
|
||||
|
||||
install:
|
||||
- pip install tox
|
||||
|
||||
script:
|
||||
- tox
|
||||
|
|
65
README.md
65
README.md
|
@ -1,42 +1,43 @@
|
|||
# YunoHost core
|
||||
<p align="center">
|
||||
<img alt="YunoHost" src="https://raw.githubusercontent.com/YunoHost/doc/master/images/logo_roundcorner.png" width="100px" />
|
||||
</p>
|
||||
|
||||
- [YunoHost project website](https://yunohost.org)
|
||||
<h1 align="center">YunoHost</h1>
|
||||
|
||||
This repository is the core of YunoHost code.
|
||||
<div align="center">
|
||||
|
||||
<a href="https://translate.yunohost.org/engage/yunohost/?utm_source=widget">
|
||||
<img src="https://translate.yunohost.org/widgets/yunohost/-/287x66-white.png" alt="Translation status" />
|
||||
</a>
|
||||
[](https://travis-ci.org/YunoHost/yunohost)
|
||||
[](https://github.com/YunoHost/yunohost/blob/stretch-unstable/LICENSE)
|
||||
[](https://mastodon.social/@yunohost)
|
||||
|
||||
## Issues
|
||||
- [Please report issues on YunoHost bugtracker](https://github.com/YunoHost/issues).
|
||||
</div>
|
||||
|
||||
## Contribute
|
||||
- You can develop on this repository using [ynh-dev tool](https://github.com/YunoHost/ynh-dev) with `use-git` sub-command.
|
||||
- On this repository we are [following this workflow](https://yunohost.org/#/build_system_en): `stable <— testing <— branch`.
|
||||
- Note: if you modify python scripts, you will have to modifiy the actions map.
|
||||
YunoHost is an operating system aiming to simplify as much as possible the administration of a server.
|
||||
|
||||
## Repository content
|
||||
- [YunoHost core Python 2.7 scripts](https://github.com/YunoHost/yunohost/tree/stable/src/yunohost).
|
||||
- [An actionsmap](https://github.com/YunoHost/yunohost/blob/stable/data/actionsmap/yunohost.yml) used by moulinette.
|
||||
- [Services configuration templates](https://github.com/YunoHost/yunohost/tree/stable/data/templates).
|
||||
- [Hooks](https://github.com/YunoHost/yunohost/tree/stable/data/hooks).
|
||||
- [Locales](https://github.com/YunoHost/yunohost/tree/stable/locales) for translations of `yunohost` command.
|
||||
- [Shell helpers](https://github.com/YunoHost/yunohost/tree/stable/data/helpers.d) for [application packaging](https://yunohost.org/#/packaging_apps_helpers_en).
|
||||
- [Modules for the XMPP server Metronome](https://github.com/YunoHost/yunohost/tree/stable/lib/metronome/modules).
|
||||
- [Debian files](https://github.com/YunoHost/yunohost/tree/stable/debian) for package creation.
|
||||
This repository corresponds to the core code of YunoHost, mainly written in Python and Bash.
|
||||
|
||||
## How does it work?
|
||||
- Python core scripts are accessible through two interfaces thanks to the [moulinette framework](https://github.com/YunoHost/moulinette):
|
||||
- [CLI](https://en.wikipedia.org/wiki/Command-line_interface) for `yunohost` command.
|
||||
- [API](https://en.wikipedia.org/wiki/Application_programming_interface) for [web administration module](https://github.com/YunoHost/yunohost-admin) (other modules could be implemented).
|
||||
- You can find more details about how YunoHost works on this [documentation (in french)](https://yunohost.org/#/package_list_fr).
|
||||
- [Project features](https://yunohost.org/#/whatsyunohost)
|
||||
- [Project website](https://yunohost.org)
|
||||
- [Install documentation](https://yunohost.org/install)
|
||||
- [Issue tracker](https://github.com/YunoHost/issues)
|
||||
|
||||
## Dependencies
|
||||
- [Python 2.7](https://www.python.org/download/releases/2.7)
|
||||
- [Moulinette](https://github.com/YunoHost/moulinette)
|
||||
- [Bash](https://www.gnu.org/software/bash/bash.html)
|
||||
- [Debian Jessie](https://www.debian.org/releases/jessie)
|
||||
# Screenshots
|
||||
|
||||
Webadmin ([Yunohost-Admin](https://github.com/YunoHost/yunohost-admin)) | Single sign-on user portal ([SSOwat](https://github.com/YunoHost/ssowat))
|
||||
--- | ---
|
||||
 | 
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
- You can learn how to get started with developing on YunoHost by reading [this piece of documentation](https://yunohost.org/dev).
|
||||
- Come chat with us on the [dev chatroom](https://yunohost.org/#/chat_rooms) !
|
||||
- You can help translate YunoHost on our [translation platform](https://translate.yunohost.org/engage/yunohost/?utm_source=widget)
|
||||
|
||||
<p align="center">
|
||||
<img src="https://translate.yunohost.org/widgets/yunohost/-/multi-auto.svg" alt="Translation status" />
|
||||
</p>
|
||||
|
||||
## License
|
||||
As [other components of YunoHost core code](https://yunohost.org/#/faq_en), this repository is under GNU AGPL v.3 license.
|
||||
|
||||
As [other components of YunoHost](https://yunohost.org/#/faq_en), this repository is licensed under GNU AGPL v3.
|
||||
|
|
|
@ -201,24 +201,34 @@ user:
|
|||
|
||||
subcategories:
|
||||
group:
|
||||
subcategory_help: Manage group
|
||||
subcategory_help: Manage user groups
|
||||
actions:
|
||||
### user_group_list()
|
||||
list:
|
||||
action_help: List group
|
||||
action_help: List existing groups
|
||||
api: GET /users/groups
|
||||
arguments:
|
||||
--fields:
|
||||
help: fields to fetch
|
||||
nargs: "+"
|
||||
-s:
|
||||
full: --short
|
||||
help: List only the names of groups
|
||||
action: store_true
|
||||
-f:
|
||||
full: --full
|
||||
help: Display all informations known about each groups
|
||||
action: store_true
|
||||
-p:
|
||||
full: --include-primary-groups
|
||||
help: Also display primary groups (each user has an eponym group that only contains itself)
|
||||
action: store_true
|
||||
default: false
|
||||
|
||||
### user_group_add()
|
||||
add:
|
||||
### user_group_create()
|
||||
create:
|
||||
action_help: Create group
|
||||
api: POST /users/groups
|
||||
arguments:
|
||||
groupname:
|
||||
help: The unique group name to add
|
||||
help: Name of the group to be created
|
||||
extra:
|
||||
pattern: &pattern_groupname
|
||||
- !!str ^[a-z0-9_]+$
|
||||
|
@ -230,7 +240,7 @@ user:
|
|||
api: DELETE /users/groups/<groupname>
|
||||
arguments:
|
||||
groupname:
|
||||
help: Username to delete
|
||||
help: Name of the group to be deleted
|
||||
extra:
|
||||
pattern: *pattern_groupname
|
||||
|
||||
|
@ -240,19 +250,19 @@ user:
|
|||
api: PUT /users/groups/<groupname>
|
||||
arguments:
|
||||
groupname:
|
||||
help: Username to update
|
||||
help: Name of the group to be updated
|
||||
extra:
|
||||
pattern: *pattern_groupname
|
||||
-a:
|
||||
full: --add-user
|
||||
help: User to add in group
|
||||
full: --add
|
||||
help: User(s) to add in the group
|
||||
nargs: "*"
|
||||
metavar: USERNAME
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
-r:
|
||||
full: --remove-user
|
||||
help: User to remove in group
|
||||
full: --remove
|
||||
help: User(s) to remove in the group
|
||||
nargs: "*"
|
||||
metavar: USERNAME
|
||||
extra:
|
||||
|
@ -260,112 +270,62 @@ user:
|
|||
|
||||
### user_group_info()
|
||||
info:
|
||||
action_help: Get group information
|
||||
action_help: Get information about a specific group
|
||||
api: GET /users/groups/<groupname>
|
||||
arguments:
|
||||
groupname:
|
||||
help: Groupname to get information
|
||||
help: Name of the group to fetch info about
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
|
||||
permission:
|
||||
subcategory_help: Manage user permission
|
||||
subcategory_help: Manage permissions
|
||||
actions:
|
||||
|
||||
### user_permission_list()
|
||||
list:
|
||||
action_help: List access to user and group
|
||||
api: GET /users/permission/<app>
|
||||
action_help: List permissions and corresponding accesses
|
||||
api: GET /users/permissions
|
||||
arguments:
|
||||
-s:
|
||||
full: --short
|
||||
help: Only list permission names
|
||||
action: store_true
|
||||
-f:
|
||||
full: --full
|
||||
help: Display all info known about each permission, including the full user list of each group it is granted to.
|
||||
action: store_true
|
||||
|
||||
|
||||
### user_permission_update()
|
||||
update:
|
||||
action_help: Manage group or user permissions
|
||||
api: PUT /users/permissions/<permission>
|
||||
arguments:
|
||||
permission:
|
||||
help: Permission to manage (e.g. mail or nextcloud or wordpress.editors)
|
||||
-a:
|
||||
full: --app
|
||||
help: Application to manage the permission
|
||||
full: --add
|
||||
help: Group or usernames to grant this permission to
|
||||
nargs: "*"
|
||||
metavar: APP
|
||||
-p:
|
||||
full: --permission
|
||||
help: Name of permission (main by default)
|
||||
nargs: "*"
|
||||
metavar: PERMISSION
|
||||
-u:
|
||||
full: --username
|
||||
help: Username
|
||||
nargs: "*"
|
||||
metavar: USER
|
||||
-g:
|
||||
full: --group
|
||||
help: Group name
|
||||
nargs: "*"
|
||||
metavar: GROUP
|
||||
|
||||
### user_permission_add()
|
||||
add:
|
||||
action_help: Grant access right to users and group
|
||||
api: POST /users/permission/<app>
|
||||
arguments:
|
||||
app:
|
||||
help: Application to manage the permission
|
||||
nargs: "+"
|
||||
-p:
|
||||
full: --permission
|
||||
help: Name of permission (main by default)
|
||||
nargs: "*"
|
||||
metavar: PERMISSION
|
||||
-u:
|
||||
full: --username
|
||||
help: Username
|
||||
nargs: "*"
|
||||
metavar: USER
|
||||
metavar: GROUP_OR_USER
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
-g:
|
||||
full: --group
|
||||
help: Group name
|
||||
-r:
|
||||
full: --remove
|
||||
help: Group or usernames revoke this permission from
|
||||
nargs: "*"
|
||||
metavar: GROUP
|
||||
metavar: GROUP_OR_USER
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
|
||||
### user_permission_remove()
|
||||
remove:
|
||||
action_help: Revoke access right to users and group
|
||||
api: PUT /users/permission/<app>
|
||||
## user_permission_reset()
|
||||
reset:
|
||||
action_help: Reset allowed groups to the default (all_users) for a given permission
|
||||
api: DELETE /users/permissions/<app>
|
||||
arguments:
|
||||
app:
|
||||
help: Application to manage the permission
|
||||
nargs: "+"
|
||||
-p:
|
||||
full: --permission
|
||||
help: Name of permission (main by default)
|
||||
nargs: "*"
|
||||
metavar: PERMISSION
|
||||
-u:
|
||||
full: --username
|
||||
help: Username
|
||||
nargs: "*"
|
||||
metavar: USER
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
-g:
|
||||
full: --group
|
||||
help: Group name
|
||||
nargs: "*"
|
||||
metavar: GROUP
|
||||
extra:
|
||||
pattern: *pattern_username
|
||||
|
||||
## user_permission_clear()
|
||||
clear:
|
||||
action_help: Reset access rights for the app
|
||||
api: DELETE /users/permission/<app>
|
||||
arguments:
|
||||
app:
|
||||
help: Application to manage the permission
|
||||
nargs: "+"
|
||||
-p:
|
||||
full: --permission
|
||||
help: Name of permission (main by default)
|
||||
nargs: "*"
|
||||
metavar: PERMISSION
|
||||
permission:
|
||||
help: Permission to manage (e.g. mail or nextcloud or wordpress.editors)
|
||||
|
||||
ssh:
|
||||
subcategory_help: Manage ssh access
|
||||
|
@ -468,7 +428,7 @@ domain:
|
|||
|
||||
### domain_dns_conf()
|
||||
dns-conf:
|
||||
action_help: Generate DNS configuration for a domain
|
||||
action_help: Generate sample DNS configuration for a domain
|
||||
api: GET /domains/<domain>/dns
|
||||
arguments:
|
||||
domain:
|
||||
|
@ -481,6 +441,21 @@ domain:
|
|||
- !!str ^[0-9]+$
|
||||
- "pattern_positive_number"
|
||||
|
||||
### domain_maindomain()
|
||||
main-domain:
|
||||
action_help: Check the current main domain, or change it
|
||||
deprecated_alias:
|
||||
- maindomain
|
||||
api:
|
||||
- GET /domains/main
|
||||
- PUT /domains/main
|
||||
arguments:
|
||||
-n:
|
||||
full: --new-main-domain
|
||||
help: Change the current main domain
|
||||
extra:
|
||||
pattern: *pattern_domain
|
||||
|
||||
### certificate_status()
|
||||
cert-status:
|
||||
action_help: List status of current certificates (all by default).
|
||||
|
@ -752,15 +727,6 @@ app:
|
|||
help: The path to be registered (e.g. /coffee)
|
||||
|
||||
|
||||
### app_debug()
|
||||
debug:
|
||||
action_help: Display all debug informations for an application
|
||||
api: GET /apps/<app>/debug
|
||||
arguments:
|
||||
app:
|
||||
help: App name
|
||||
|
||||
|
||||
### app_makedefault()
|
||||
makedefault:
|
||||
action_help: Redirect domain root to an app
|
||||
|
@ -791,6 +757,7 @@ app:
|
|||
addaccess:
|
||||
action_help: Grant access right to users (everyone by default)
|
||||
api: PUT /access
|
||||
deprecated: true
|
||||
arguments:
|
||||
apps:
|
||||
nargs: "+"
|
||||
|
@ -802,6 +769,7 @@ app:
|
|||
removeaccess:
|
||||
action_help: Revoke access right to users (everyone by default)
|
||||
api: DELETE /access
|
||||
deprecated: true
|
||||
arguments:
|
||||
apps:
|
||||
nargs: "+"
|
||||
|
@ -813,6 +781,7 @@ app:
|
|||
clearaccess:
|
||||
action_help: Reset access rights for the app
|
||||
api: POST /access
|
||||
deprecated: true
|
||||
arguments:
|
||||
apps:
|
||||
nargs: "+"
|
||||
|
@ -964,147 +933,6 @@ backup:
|
|||
pattern: *pattern_backup_archive_name
|
||||
|
||||
|
||||
#############################
|
||||
# Monitor #
|
||||
#############################
|
||||
monitor:
|
||||
category_help: Monitor the server
|
||||
actions:
|
||||
|
||||
### monitor_disk()
|
||||
disk:
|
||||
action_help: Monitor disk space and usage
|
||||
api: GET /monitor/disk
|
||||
arguments:
|
||||
-f:
|
||||
full: --filesystem
|
||||
help: Show filesystem disk space
|
||||
action: append_const
|
||||
const: filesystem
|
||||
dest: units
|
||||
-t:
|
||||
full: --io
|
||||
help: Show I/O throughput
|
||||
action: append_const
|
||||
const: io
|
||||
dest: units
|
||||
-m:
|
||||
full: --mountpoint
|
||||
help: Monitor only the device mounted on MOUNTPOINT
|
||||
action: store
|
||||
-H:
|
||||
full: --human-readable
|
||||
help: Print sizes in human readable format
|
||||
action: store_true
|
||||
|
||||
### monitor_network()
|
||||
network:
|
||||
action_help: Monitor network interfaces
|
||||
api: GET /monitor/network
|
||||
arguments:
|
||||
-u:
|
||||
full: --usage
|
||||
help: Show interfaces bit rates
|
||||
action: append_const
|
||||
const: usage
|
||||
dest: units
|
||||
-i:
|
||||
full: --infos
|
||||
help: Show network informations
|
||||
action: append_const
|
||||
const: infos
|
||||
dest: units
|
||||
-c:
|
||||
full: --check
|
||||
help: Check network configuration
|
||||
action: append_const
|
||||
const: check
|
||||
dest: units
|
||||
-H:
|
||||
full: --human-readable
|
||||
help: Print sizes in human readable format
|
||||
action: store_true
|
||||
|
||||
### monitor_system()
|
||||
system:
|
||||
action_help: Monitor system informations and usage
|
||||
api: GET /monitor/system
|
||||
arguments:
|
||||
-m:
|
||||
full: --memory
|
||||
help: Show memory usage
|
||||
action: append_const
|
||||
const: memory
|
||||
dest: units
|
||||
-c:
|
||||
full: --cpu
|
||||
help: Show CPU usage and load
|
||||
action: append_const
|
||||
const: cpu
|
||||
dest: units
|
||||
-p:
|
||||
full: --process
|
||||
help: Show processes summary
|
||||
action: append_const
|
||||
const: process
|
||||
dest: units
|
||||
-u:
|
||||
full: --uptime
|
||||
help: Show the system uptime
|
||||
action: append_const
|
||||
const: uptime
|
||||
dest: units
|
||||
-i:
|
||||
full: --infos
|
||||
help: Show system informations
|
||||
action: append_const
|
||||
const: infos
|
||||
dest: units
|
||||
-H:
|
||||
full: --human-readable
|
||||
help: Print sizes in human readable format
|
||||
action: store_true
|
||||
|
||||
### monitor_updatestats()
|
||||
update-stats:
|
||||
action_help: Update monitoring statistics
|
||||
api: POST /monitor/stats
|
||||
arguments:
|
||||
period:
|
||||
help: Time period to update
|
||||
choices:
|
||||
- day
|
||||
- week
|
||||
- month
|
||||
|
||||
### monitor_showstats()
|
||||
show-stats:
|
||||
action_help: Show monitoring statistics
|
||||
api: GET /monitor/stats
|
||||
arguments:
|
||||
period:
|
||||
help: Time period to show
|
||||
choices:
|
||||
- day
|
||||
- week
|
||||
- month
|
||||
|
||||
### monitor_enable()
|
||||
enable:
|
||||
action_help: Enable server monitoring
|
||||
api: PUT /monitor
|
||||
arguments:
|
||||
-s:
|
||||
full: --with-stats
|
||||
help: Enable monitoring statistics
|
||||
action: store_true
|
||||
|
||||
### monitor_disable()
|
||||
disable:
|
||||
api: DELETE /monitor
|
||||
action_help: Disable server monitoring
|
||||
|
||||
|
||||
#############################
|
||||
# Settings #
|
||||
#############################
|
||||
|
@ -1492,6 +1320,14 @@ dyndns:
|
|||
-i:
|
||||
full: --ipv4
|
||||
help: IP address to send
|
||||
-f:
|
||||
full: --force
|
||||
help: Force the update (for debugging only)
|
||||
action: store_true
|
||||
-D:
|
||||
full: --dry-run
|
||||
help: Only display the generated zone
|
||||
action: store_true
|
||||
-6:
|
||||
full: --ipv6
|
||||
help: IPv6 address to send
|
||||
|
@ -1530,12 +1366,9 @@ tools:
|
|||
### tools_maindomain()
|
||||
maindomain:
|
||||
action_help: Check the current main domain, or change it
|
||||
api:
|
||||
- GET /domains/main
|
||||
- PUT /domains/main
|
||||
arguments:
|
||||
-n:
|
||||
full: --new-domain
|
||||
full: --new-main-domain
|
||||
help: Change the current main domain
|
||||
extra:
|
||||
pattern: *pattern_domain
|
||||
|
@ -1594,16 +1427,6 @@ tools:
|
|||
help: Upgrade only the system packages
|
||||
action: store_true
|
||||
|
||||
### tools_diagnosis()
|
||||
diagnosis:
|
||||
action_help: YunoHost diagnosis
|
||||
api: GET /diagnosis
|
||||
arguments:
|
||||
-p:
|
||||
full: --private
|
||||
help: Show private data (domain, IP)
|
||||
action: store_true
|
||||
|
||||
### tools_shell()
|
||||
shell:
|
||||
action_help: Launch a development shell
|
||||
|
@ -1678,22 +1501,23 @@ tools:
|
|||
|
||||
### tools_migrations_migrate()
|
||||
migrate:
|
||||
action_help: Perform migrations
|
||||
action_help: Run migrations
|
||||
api: POST /migrations/migrate
|
||||
arguments:
|
||||
-t:
|
||||
help: target migration number (or 0), latest one by default
|
||||
type: int
|
||||
full: --target
|
||||
-s:
|
||||
help: skip the migration(s), use it only if you know what you are doing
|
||||
full: --skip
|
||||
targets:
|
||||
help: Migrations to run (all pendings by default)
|
||||
nargs: "*"
|
||||
--skip:
|
||||
help: Skip specified migrations (to be used only if you know what you are doing)
|
||||
action: store_true
|
||||
--force-rerun:
|
||||
help: Re-run already-ran specified migration (to be used only if you know what you are doing)
|
||||
action: store_true
|
||||
--auto:
|
||||
help: automatic mode, won't run manual migrations, use it only if you know what you are doing
|
||||
help: Automatic mode, won't run manual migrations (to be used only if you know what you are doing)
|
||||
action: store_true
|
||||
--accept-disclaimer:
|
||||
help: accept disclaimers of migration (please read them before using this option)
|
||||
help: Accept disclaimers of migrations (please read them before using this option)
|
||||
action: store_true
|
||||
|
||||
### tools_migrations_state()
|
||||
|
@ -1838,3 +1662,59 @@ log:
|
|||
--share:
|
||||
help: Share the full log using yunopaste
|
||||
action: store_true
|
||||
|
||||
|
||||
#############################
|
||||
# Diagnosis #
|
||||
#############################
|
||||
diagnosis:
|
||||
category_help: Look for possible issues on the server
|
||||
actions:
|
||||
|
||||
list:
|
||||
action_help: List diagnosis categories
|
||||
api: GET /diagnosis/list
|
||||
|
||||
show:
|
||||
action_help: Show most recents diagnosis results
|
||||
api: GET /diagnosis/show
|
||||
arguments:
|
||||
categories:
|
||||
help: Diagnosis categories to display (all by default)
|
||||
nargs: "*"
|
||||
--full:
|
||||
help: Display additional information
|
||||
action: store_true
|
||||
--issues:
|
||||
help: Only display issues
|
||||
action: store_true
|
||||
--share:
|
||||
help: Share the logs using yunopaste
|
||||
action: store_true
|
||||
|
||||
run:
|
||||
action_help: Show most recents diagnosis results
|
||||
api: POST /diagnosis/run
|
||||
arguments:
|
||||
categories:
|
||||
help: Diagnosis categories to run (all by default)
|
||||
nargs: "*"
|
||||
--force:
|
||||
help: Ignore the cached report even if it is still 'fresh'
|
||||
action: store_true
|
||||
|
||||
ignore:
|
||||
action_help: Configure some diagnosis results to be ignored and therefore not considered as actual issues
|
||||
api: POST /diagnosis/ignore
|
||||
arguments:
|
||||
--add-filter:
|
||||
help: "Add a filter. The first element should be a diagnosis category, and other criterias can be provided using the infos from the 'meta' sections in 'yunohost diagnosis show'. For example: 'dnsrecords domain=yolo.test category=xmpp'"
|
||||
nargs: "*"
|
||||
metavar: CRITERIA
|
||||
--remove-filter:
|
||||
help: Remove a filter (it should be an existing filter as listed with --list)
|
||||
nargs: "*"
|
||||
metavar: CRITERIA
|
||||
--list:
|
||||
help: List active ignore filters
|
||||
action: store_true
|
||||
|
|
|
@ -218,6 +218,27 @@ ynh_install_app_dependencies () {
|
|||
fi
|
||||
local dep_app=${app//_/-} # Replace all '_' by '-'
|
||||
|
||||
#
|
||||
# Epic ugly hack to fix the goddamn dependency nightmare of sury
|
||||
# Sponsored by the "Djeezusse Fokin Kraiste Why Do Adminsys Has To Be So Fucking Complicated I Should Go Grow Potatoes Instead Of This Shit" collective
|
||||
# https://github.com/YunoHost/issues/issues/1407
|
||||
#
|
||||
# If we require to install php dependency
|
||||
if echo $dependencies | grep -q 'php';
|
||||
then
|
||||
# And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33-0 on debian)
|
||||
if dpkg --list | grep "php7.0" | grep -q -v "7.0.33-0+deb9"
|
||||
then
|
||||
# And sury ain't already installed
|
||||
if ! grep -nrq "sury" /etc/apt/sources.list*
|
||||
then
|
||||
# Re-add sury
|
||||
echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/sury.list
|
||||
wget -O /etc/apt/trusted.gpg.d/sury.gpg https://packages.sury.org/php/apt.gpg
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build
|
||||
Section: misc
|
||||
Priority: optional
|
||||
|
|
|
@ -339,7 +339,7 @@ ynh_backup_if_checksum_is_different () {
|
|||
backup_file_checksum=""
|
||||
if [ -n "$checksum_value" ]
|
||||
then # Proceed only if a value was stored into the app settings
|
||||
if ! echo "$checksum_value $file" | sudo md5sum -c --status
|
||||
if [ -e $file ] && ! echo "$checksum_value $file" | sudo md5sum -c --status
|
||||
then # If the checksum is now different
|
||||
backup_file_checksum="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')"
|
||||
sudo mkdir -p "$(dirname "$backup_file_checksum")"
|
||||
|
|
|
@ -40,10 +40,13 @@ ynh_use_logrotate () {
|
|||
fi
|
||||
|
||||
if [ $# -gt 0 ] && [ "$(echo ${1:0:1})" != "-" ]; then
|
||||
if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile
|
||||
local logfile=$1 # In this case, focus logrotate on the logfile
|
||||
# If the given logfile parameter already exists as a file, or if it ends up with ".log",
|
||||
# we just want to manage a single file
|
||||
if [ -f "$1" ] || [ "$(echo ${1##*.})" == "log" ]; then
|
||||
local logfile=$1
|
||||
# Otherwise we assume we want to manage a directory and all its .log file inside
|
||||
else
|
||||
local logfile=$1/*.log # Else, uses the directory and all logfile into it.
|
||||
local logfile=$1/*.log
|
||||
fi
|
||||
fi
|
||||
# LEGACY CODE
|
||||
|
@ -54,7 +57,7 @@ ynh_use_logrotate () {
|
|||
fi
|
||||
if [ -n "$logfile" ]
|
||||
then
|
||||
if [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile
|
||||
if [ ! -f "$1" ] && [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile
|
||||
local logfile="$logfile/*.log" # Else, uses the directory and all logfile into it.
|
||||
fi
|
||||
else
|
||||
|
|
|
@ -231,7 +231,7 @@ ynh_mysql_remove_db () {
|
|||
fi
|
||||
|
||||
# Remove mysql user if it exists
|
||||
if $(ynh_mysql_user_exists --user=$db_user); then
|
||||
if ynh_mysql_user_exists --user=$db_user; then
|
||||
ynh_mysql_drop_user $db_user
|
||||
fi
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@ ynh_install_n () {
|
|||
ynh_print_info --message="Installation of N - Node.js version management"
|
||||
# Build an app.src for n
|
||||
mkdir -p "../conf"
|
||||
echo "SOURCE_URL=https://github.com/tj/n/archive/v2.1.7.tar.gz
|
||||
SOURCE_SUM=2ba3c9d4dd3c7e38885b37e02337906a1ee91febe6d5c9159d89a9050f2eea8f" > "../conf/n.src"
|
||||
echo "SOURCE_URL=https://github.com/tj/n/archive/v4.1.0.tar.gz
|
||||
SOURCE_SUM=3983fa3f00d4bf85ba8e21f1a590f6e28938093abe0bb950aeea52b1717471fc" > "../conf/n.src"
|
||||
# Download and extract n
|
||||
ynh_setup_source --dest_dir="$n_install_dir/git" --source_id=n
|
||||
# Install n
|
||||
|
|
|
@ -283,11 +283,11 @@ ynh_psql_test_if_first_run() {
|
|||
|
||||
sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$psql_root_password'" postgres
|
||||
|
||||
# force all user to connect to local database using passwords
|
||||
# force all user to connect to local databases using hashed passwords
|
||||
# https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html#EXAMPLE-PG-HBA.CONF
|
||||
# Note: we can't use peer since YunoHost create users with nologin
|
||||
# See: https://github.com/YunoHost/yunohost/blob/unstable/data/helpers.d/user
|
||||
ynh_replace_string --match_string="local\(\s*\)all\(\s*\)all\(\s*\)peer" --replace_string="local\1all\2all\3password" --target_file="$pg_hba"
|
||||
ynh_replace_string --match_string="local\(\s*\)all\(\s*\)all\(\s*\)peer" --replace_string="local\1all\2all\3md5" --target_file="$pg_hba"
|
||||
|
||||
# Advertise service in admin panel
|
||||
yunohost service add postgresql --log "$logfile"
|
||||
|
|
|
@ -159,7 +159,7 @@ ynh_add_protected_uris() {
|
|||
ynh_app_setting()
|
||||
{
|
||||
ACTION="$1" APP="$2" KEY="$3" VALUE="${4:-}" python - <<EOF
|
||||
import os, yaml
|
||||
import os, yaml, sys
|
||||
app, action = os.environ['APP'], os.environ['ACTION'].lower()
|
||||
key, value = os.environ['KEY'], os.environ.get('VALUE', None)
|
||||
setting_file = "/etc/yunohost/apps/%s/settings.yml" % app
|
||||
|
@ -176,12 +176,21 @@ else:
|
|||
elif action == "set":
|
||||
if key in ['redirected_urls', 'redirected_regex']:
|
||||
value = yaml.load(value)
|
||||
if any(key.startswith(word+"_") for word in ["unprotected", "protected", "skipped"]):
|
||||
sys.stderr.write("/!\\ Packagers! This app is still using the skipped/protected/unprotected_uris/regex settings which are now obsolete and deprecated... Instead, you should use the new helpers 'ynh_permission_{create,urls,update,delete}' and the 'visitors' group to initialize the public/private access. Check out the documentation at the bottom of yunohost.org/groups_and_permissions to learn how to use the new permission mechanism.\n")
|
||||
settings[key] = value
|
||||
else:
|
||||
raise ValueError("action should either be get, set or delete")
|
||||
with open(setting_file, "w") as f:
|
||||
yaml.safe_dump(settings, f, default_flow_style=False)
|
||||
EOF
|
||||
|
||||
# Fucking legacy permission management.
|
||||
# We need this because app temporarily set the app as unprotected to configure it with curl...
|
||||
if [[ "$3" =~ ^(unprotected|skipped)_ ]] && [[ "${4:-}" == "/" ]]
|
||||
then
|
||||
ynh_permission_update --permission "main" --remove "all_users" --add "visitors"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check availability of a web path
|
||||
|
@ -230,70 +239,102 @@ ynh_webpath_register () {
|
|||
|
||||
# Create a new permission for the app
|
||||
#
|
||||
# usage: ynh_permission_create --app "app" --permission "permission" --defaultdisallow [--urls "url" ["url" ...]]
|
||||
# | arg: app - the application id
|
||||
# example: ynh_permission_create --permission admin --url /admin --allowed alice bob
|
||||
#
|
||||
# usage: ynh_permission_create --permission "permission" [--url "url"] [--allowed group1 group2]
|
||||
# | arg: permission - the name for the permission (by default a permission named "main" already exist)
|
||||
# | arg: defaultdisallow - define if all user will be allowed by default
|
||||
# | arg: urls - the list of urls for the the permission
|
||||
# | arg: url - (optional) URL for which access will be allowed/forbidden
|
||||
# | arg: allowed - (optional) A list of group/user to allow for the permission
|
||||
#
|
||||
# If provided, 'url' is assumed to be relative to the app domain/path if they
|
||||
# start with '/'. For example:
|
||||
# / -> domain.tld/app
|
||||
# /admin -> domain.tld/app/admin
|
||||
# domain.tld/app/api -> domain.tld/app/api
|
||||
#
|
||||
# 'url' can be later treated as a regex if it starts with "re:".
|
||||
# For example:
|
||||
# re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$
|
||||
# re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$
|
||||
#
|
||||
ynh_permission_create() {
|
||||
declare -Ar args_array=( [a]=app= [p]=permission= [d]=defaultdisallow [u]=urls= )
|
||||
local app
|
||||
declare -Ar args_array=( [p]=permission= [u]=url= [a]=allowed= )
|
||||
local permission
|
||||
local defaultdisallow
|
||||
local urls
|
||||
local url
|
||||
local allowed
|
||||
ynh_handle_getopts_args "$@"
|
||||
if [[ -n ${defaultdisallow:-} ]]; then
|
||||
defaultdisallow=",default_allow=False"
|
||||
|
||||
if [[ -n ${url:-} ]]; then
|
||||
url="'$url'"
|
||||
else
|
||||
url="None"
|
||||
fi
|
||||
|
||||
if [[ -n ${urls:-} ]]; then
|
||||
urls=",urls=['${urls//';'/"','"}']"
|
||||
if [[ -n ${allowed:-} ]]; then
|
||||
allowed=",allowed=['${allowed//';'/"','"}']"
|
||||
fi
|
||||
yunohost tools shell -c "from yunohost.permission import permission_add; permission_add('$app', '$permission' ${defaultdisallow:-} ${urls:-}, sync_perm=False)"
|
||||
|
||||
yunohost tools shell -c "from yunohost.permission import permission_create; permission_create('$app.$permission', url=$url ${allowed:-} , sync_perm=False)"
|
||||
}
|
||||
|
||||
# Remove a permission for the app (note that when the app is removed all permission is automatically removed)
|
||||
#
|
||||
# usage: ynh_permission_remove --app "app" --permission "permission"
|
||||
# | arg: app - the application id
|
||||
# example: ynh_permission_delete --permission editors
|
||||
#
|
||||
# usage: ynh_permission_delete --permission "permission"
|
||||
# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed)
|
||||
ynh_permission_remove() {
|
||||
declare -Ar args_array=( [a]=app= [p]=permission= )
|
||||
local app
|
||||
#
|
||||
ynh_permission_delete() {
|
||||
declare -Ar args_array=( [p]=permission= )
|
||||
local permission
|
||||
ynh_handle_getopts_args "$@"
|
||||
|
||||
yunohost tools shell -c "from yunohost.permission import permission_remove; permission_remove('$app', '$permission', sync_perm=False)"
|
||||
yunohost tools shell -c "from yunohost.permission import permission_delete; permission_delete('$app.$permission', sync_perm=False)"
|
||||
}
|
||||
|
||||
# Add a path managed by the SSO
|
||||
# Redefine the url associated to a permission
|
||||
#
|
||||
# usage: ynh_permission_add_path --app "app" --permission "permission" --url "url" ["url" ...]
|
||||
# | arg: app - the application id
|
||||
# | arg: permission - the name for the permission
|
||||
# | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin)
|
||||
ynh_permission_add_path() {
|
||||
declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= )
|
||||
local app
|
||||
# usage: ynh_permission_url --permission "permission" --url "url"
|
||||
# | arg: permission - the name for the permission (by default a permission named "main" is removed automatically when the app is removed)
|
||||
# | arg: url - (optional) URL for which access will be allowed/forbidden
|
||||
#
|
||||
ynh_permission_url() {
|
||||
declare -Ar args_array=([p]=permission= [u]=url=)
|
||||
local permission
|
||||
local url
|
||||
ynh_handle_getopts_args "$@"
|
||||
|
||||
yunohost tools shell -c "from yunohost.permission import permission_update; permission_update('$app', '$permission', add_url=['${url//';'/"','"}'], sync_perm=False)"
|
||||
if [[ -n ${url:-} ]]; then
|
||||
url="'$url'"
|
||||
else
|
||||
url="None"
|
||||
fi
|
||||
|
||||
yunohost tools shell -c "from yunohost.permission import permission_url; permission_url('$app.$permission', url=$url)"
|
||||
}
|
||||
|
||||
# Remove a path managed by the SSO
|
||||
|
||||
# Update a permission for the app
|
||||
#
|
||||
# usage: ynh_permission_del_path --app "app" --permission "permission" --url "url" ["url" ...]
|
||||
# | arg: app - the application id
|
||||
# | arg: permission - the name for the permission
|
||||
# | arg: url - the FULL url for the the permission (ex domain.tld/apps/admin)
|
||||
ynh_permission_del_path() {
|
||||
declare -Ar args_array=( [a]=app= [p]=permission= [u]=url= )
|
||||
local app
|
||||
# usage: ynh_permission_update --permission "permission" --add "group" ["group" ...] --remove "group" ["group" ...]
|
||||
# | arg: permission - the name for the permission (by default a permission named "main" already exist)
|
||||
# | arg: add - the list of group or users to enable add to the permission
|
||||
# | arg: remove - the list of group or users to remove from the permission
|
||||
#
|
||||
# example: ynh_permission_update --permission admin --add samdoe --remove all_users
|
||||
ynh_permission_update() {
|
||||
declare -Ar args_array=( [p]=permission= [a]=add= [r]=remove= )
|
||||
local permission
|
||||
local url
|
||||
local add
|
||||
local remove
|
||||
ynh_handle_getopts_args "$@"
|
||||
|
||||
yunohost tools shell -c "from yunohost.permission import permission_update; permission_update('$app', '$permission', remove_url=['${url//';'/"','"}'], sync_perm=False)"
|
||||
if [[ -n ${add:-} ]]; then
|
||||
add="--add ${add//';'/" "}"
|
||||
fi
|
||||
if [[ -n ${remove:-} ]]; then
|
||||
remove="--remove ${remove//';'/" "} "
|
||||
fi
|
||||
|
||||
yunohost user permission update "$app.$permission" ${add:-} ${remove:-}
|
||||
}
|
||||
|
|
|
@ -53,9 +53,6 @@ do_pre_regen() {
|
|||
else
|
||||
sudo cp services.yml /etc/yunohost/services.yml
|
||||
fi
|
||||
|
||||
mkdir -p "$pending_dir"/etc/etckeeper/
|
||||
cp etckeeper.conf "$pending_dir"/etc/etckeeper/
|
||||
}
|
||||
|
||||
_update_services() {
|
||||
|
|
60
data/hooks/diagnosis/00-basesystem.py
Normal file
60
data/hooks/diagnosis/00-basesystem.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
|
||||
from moulinette.utils.filesystem import read_file
|
||||
from yunohost.diagnosis import Diagnoser
|
||||
from yunohost.utils.packages import ynh_packages_version
|
||||
|
||||
|
||||
class BaseSystemDiagnoser(Diagnoser):
|
||||
|
||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||
cache_duration = 3600 * 24
|
||||
dependencies = []
|
||||
|
||||
def run(self):
|
||||
|
||||
# Kernel version
|
||||
kernel_version = read_file('/proc/sys/kernel/osrelease').strip()
|
||||
yield dict(meta={"test": "kernel"},
|
||||
status="INFO",
|
||||
summary=("diagnosis_basesystem_kernel", {"kernel_version": kernel_version}))
|
||||
|
||||
# FIXME / TODO : add virt/vm technology using systemd-detect-virt and/or machine arch
|
||||
|
||||
# Debian release
|
||||
debian_version = read_file("/etc/debian_version").strip()
|
||||
yield dict(meta={"test": "host"},
|
||||
status="INFO",
|
||||
summary=("diagnosis_basesystem_host", {"debian_version": debian_version}))
|
||||
|
||||
# Yunohost packages versions
|
||||
ynh_packages = ynh_packages_version()
|
||||
# We check if versions are consistent (e.g. all 3.6 and not 3 packages with 3.6 and the other with 3.5)
|
||||
# This is a classical issue for upgrades that failed in the middle
|
||||
# (or people upgrading half of the package because they did 'apt upgrade' instead of 'dist-upgrade')
|
||||
# Here, ynh_core_version is for example "3.5.4.12", so [:3] is "3.5" and we check it's the same for all packages
|
||||
ynh_core_version = ynh_packages["yunohost"]["version"]
|
||||
consistent_versions = all(infos["version"][:3] == ynh_core_version[:3] for infos in ynh_packages.values())
|
||||
ynh_version_details = [("diagnosis_basesystem_ynh_single_version", (package, infos["version"], infos["repo"]))
|
||||
for package, infos in ynh_packages.items()]
|
||||
|
||||
if consistent_versions:
|
||||
yield dict(meta={"test": "ynh_versions"},
|
||||
data={"main_version": ynh_core_version, "repo": ynh_packages["yunohost"]["repo"]},
|
||||
status="INFO",
|
||||
summary=("diagnosis_basesystem_ynh_main_version",
|
||||
{"main_version": ynh_core_version,
|
||||
"repo": ynh_packages["yunohost"]["repo"]}),
|
||||
details=ynh_version_details)
|
||||
else:
|
||||
yield dict(meta={"test": "ynh_versions"},
|
||||
data={"main_version": ynh_core_version, "repo": ynh_packages["yunohost"]["repo"]},
|
||||
status="ERROR",
|
||||
summary=("diagnosis_basesystem_ynh_inconsistent_versions", {}),
|
||||
details=ynh_version_details)
|
||||
|
||||
|
||||
def main(args, env, loggers):
|
||||
return BaseSystemDiagnoser(args, env, loggers).diagnose()
|
150
data/hooks/diagnosis/10-ip.py
Normal file
150
data/hooks/diagnosis/10-ip.py
Normal file
|
@ -0,0 +1,150 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import random
|
||||
|
||||
from moulinette.utils.network import download_text
|
||||
from moulinette.utils.process import check_output
|
||||
from moulinette.utils.filesystem import read_file
|
||||
|
||||
from yunohost.diagnosis import Diagnoser
|
||||
|
||||
|
||||
class IPDiagnoser(Diagnoser):
|
||||
|
||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||
cache_duration = 60
|
||||
dependencies = []
|
||||
|
||||
def run(self):
|
||||
|
||||
# ############################################################ #
|
||||
# PING : Check that we can ping outside at least in ipv4 or v6 #
|
||||
# ############################################################ #
|
||||
|
||||
can_ping_ipv4 = self.can_ping_outside(4)
|
||||
can_ping_ipv6 = self.can_ping_outside(6)
|
||||
|
||||
if not can_ping_ipv4 and not can_ping_ipv6:
|
||||
yield dict(meta={"test": "ping"},
|
||||
status="ERROR",
|
||||
summary=("diagnosis_ip_not_connected_at_all", {}))
|
||||
# Not much else we can do if there's no internet at all
|
||||
return
|
||||
|
||||
# ###################################################### #
|
||||
# DNS RESOLUTION : Check that we can resolve domain name #
|
||||
# (later needed to talk to ip. and ip6.yunohost.org) #
|
||||
# ###################################################### #
|
||||
|
||||
can_resolve_dns = self.can_resolve_dns()
|
||||
|
||||
# In every case, we can check that resolvconf seems to be okay
|
||||
# (symlink managed by resolvconf service + pointing to dnsmasq)
|
||||
good_resolvconf = self.resolvconf_is_symlink() and self.resolvconf_points_to_localhost()
|
||||
|
||||
# If we can't resolve domain names at all, that's a pretty big issue ...
|
||||
# If it turns out that at the same time, resolvconf is bad, that's probably
|
||||
# the cause of this, so we use a different message in that case
|
||||
if not can_resolve_dns:
|
||||
yield dict(meta={"test": "dnsresolv"},
|
||||
status="ERROR",
|
||||
summary=("diagnosis_ip_broken_dnsresolution", {}) if good_resolvconf
|
||||
else ("diagnosis_ip_broken_resolvconf", {}))
|
||||
return
|
||||
# Otherwise, if the resolv conf is bad but we were able to resolve domain name,
|
||||
# still warn that we're using a weird resolv conf ...
|
||||
elif not good_resolvconf:
|
||||
yield dict(meta={"test": "dnsresolv"},
|
||||
status="WARNING",
|
||||
summary=("diagnosis_ip_weird_resolvconf", {}),
|
||||
details=[("diagnosis_ip_weird_resolvconf_details", ())])
|
||||
else:
|
||||
yield dict(meta={"test": "dnsresolv"},
|
||||
status="SUCCESS",
|
||||
summary=("diagnosis_ip_dnsresolution_working", {}))
|
||||
|
||||
# ##################################################### #
|
||||
# IP DIAGNOSIS : Check that we're actually able to talk #
|
||||
# to a web server to fetch current IPv4 and v6 #
|
||||
# ##################################################### #
|
||||
|
||||
ipv4 = self.get_public_ip(4) if can_ping_ipv4 else None
|
||||
ipv6 = self.get_public_ip(6) if can_ping_ipv6 else None
|
||||
|
||||
yield dict(meta={"test": "ip", "version": 4},
|
||||
data=ipv4,
|
||||
status="SUCCESS" if ipv4 else "ERROR",
|
||||
summary=("diagnosis_ip_connected_ipv4", {}) if ipv4
|
||||
else ("diagnosis_ip_no_ipv4", {}))
|
||||
|
||||
yield dict(meta={"test": "ip", "version": 6},
|
||||
data=ipv6,
|
||||
status="SUCCESS" if ipv6 else "WARNING",
|
||||
summary=("diagnosis_ip_connected_ipv6", {}) if ipv6
|
||||
else ("diagnosis_ip_no_ipv6", {}))
|
||||
|
||||
# TODO / FIXME : add some attempt to detect ISP (using whois ?) ?
|
||||
|
||||
def can_ping_outside(self, protocol=4):
|
||||
|
||||
assert protocol in [4, 6], "Invalid protocol version, it should be either 4 or 6 and was '%s'" % repr(protocol)
|
||||
|
||||
# We can know that ipv6 is not available directly if this file does not exists
|
||||
if protocol == 6 and not os.path.exists("/proc/net/if_inet6"):
|
||||
return False
|
||||
|
||||
# If we are indeed connected in ipv4 or ipv6, we should find a default route
|
||||
routes = check_output("ip -%s route" % protocol).split("\n")
|
||||
if not [r for r in routes if r.startswith("default")]:
|
||||
return False
|
||||
|
||||
# We use the resolver file as a list of well-known, trustable (ie not google ;)) IPs that we can ping
|
||||
resolver_file = "/usr/share/yunohost/templates/dnsmasq/plain/resolv.dnsmasq.conf"
|
||||
resolvers = [r.split(" ")[1] for r in read_file(resolver_file).split("\n") if r.startswith("nameserver")]
|
||||
|
||||
if protocol == 4:
|
||||
resolvers = [r for r in resolvers if ":" not in r]
|
||||
if protocol == 6:
|
||||
resolvers = [r for r in resolvers if ":" in r]
|
||||
|
||||
assert resolvers != [], "Uhoh, need at least one IPv%s DNS resolver in %s ..." % (protocol, resolver_file)
|
||||
|
||||
# So let's try to ping the first 4~5 resolvers (shuffled)
|
||||
# If we succesfully ping any of them, we conclude that we are indeed connected
|
||||
def ping(protocol, target):
|
||||
return os.system("ping%s -c1 -W 3 %s >/dev/null 2>/dev/null" % ("" if protocol == 4 else "6", target)) == 0
|
||||
|
||||
random.shuffle(resolvers)
|
||||
return any(ping(protocol, resolver) for resolver in resolvers[:5])
|
||||
|
||||
def can_resolve_dns(self):
|
||||
return os.system("dig +short ip.yunohost.org >/dev/null 2>/dev/null") == 0
|
||||
|
||||
def resolvconf_is_symlink(self):
|
||||
return os.path.realpath("/etc/resolv.conf") == "/run/resolvconf/resolv.conf"
|
||||
|
||||
def resolvconf_points_to_localhost(self):
|
||||
file_ = "/etc/resolv.conf"
|
||||
resolvers = [r.split(" ")[1] for r in read_file(file_).split("\n") if r.startswith("nameserver")]
|
||||
return resolvers == ["127.0.0.1"]
|
||||
|
||||
def get_public_ip(self, protocol=4):
|
||||
|
||||
# FIXME - TODO : here we assume that DNS resolution for ip.yunohost.org is working
|
||||
# but if we want to be able to diagnose DNS resolution issues independently from
|
||||
# internet connectivity, we gotta rely on fixed IPs first....
|
||||
|
||||
assert protocol in [4, 6], "Invalid protocol version, it should be either 4 or 6 and was '%s'" % repr(protocol)
|
||||
|
||||
url = 'https://ip%s.yunohost.org' % ('6' if protocol == 6 else '')
|
||||
|
||||
try:
|
||||
return download_text(url, timeout=30).strip()
|
||||
except Exception as e:
|
||||
self.logger_debug("Could not get public IPv%s : %s" % (str(protocol), str(e)))
|
||||
return None
|
||||
|
||||
|
||||
def main(args, env, loggers):
|
||||
return IPDiagnoser(args, env, loggers).diagnose()
|
89
data/hooks/diagnosis/12-dnsrecords.py
Normal file
89
data/hooks/diagnosis/12-dnsrecords.py
Normal file
|
@ -0,0 +1,89 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
|
||||
from moulinette.utils.process import check_output
|
||||
from moulinette.utils.filesystem import read_file
|
||||
|
||||
from yunohost.diagnosis import Diagnoser
|
||||
from yunohost.domain import domain_list, _build_dns_conf, _get_maindomain
|
||||
|
||||
|
||||
class DNSRecordsDiagnoser(Diagnoser):
|
||||
|
||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||
cache_duration = 3600 * 24
|
||||
dependencies = ["ip"]
|
||||
|
||||
def run(self):
|
||||
|
||||
resolvers = read_file("/etc/resolv.dnsmasq.conf").split("\n")
|
||||
ipv4_resolvers = [r.split(" ")[1] for r in resolvers if r.startswith("nameserver") and ":" not in r]
|
||||
# FIXME some day ... handle ipv4-only and ipv6-only servers. For now we assume we have at least ipv4
|
||||
assert ipv4_resolvers != [], "Uhoh, need at least one IPv4 DNS resolver ..."
|
||||
|
||||
self.resolver = ipv4_resolvers[0]
|
||||
main_domain = _get_maindomain()
|
||||
|
||||
all_domains = domain_list()["domains"]
|
||||
for domain in all_domains:
|
||||
self.logger_debug("Diagnosing DNS conf for %s" % domain)
|
||||
for report in self.check_domain(domain, domain == main_domain):
|
||||
yield report
|
||||
|
||||
# FIXME : somewhere, should implement a check for reverse DNS ...
|
||||
|
||||
# FIXME / TODO : somewhere, could also implement a check for domain expiring soon
|
||||
|
||||
def check_domain(self, domain, is_main_domain):
|
||||
|
||||
expected_configuration = _build_dns_conf(domain)
|
||||
|
||||
# Here if there are no AAAA record, we should add something to expect "no" AAAA record
|
||||
# to properly diagnose situations where people have a AAAA record but no IPv6
|
||||
|
||||
for category, records in expected_configuration.items():
|
||||
|
||||
discrepancies = []
|
||||
|
||||
for r in records:
|
||||
current_value = self.get_current_record(domain, r["name"], r["type"]) or "None"
|
||||
expected_value = r["value"] if r["value"] != "@" else domain + "."
|
||||
|
||||
if current_value == "None":
|
||||
discrepancies.append(("diagnosis_dns_missing_record", (r["type"], r["name"], expected_value)))
|
||||
elif current_value != expected_value:
|
||||
discrepancies.append(("diagnosis_dns_discrepancy", (r["type"], r["name"], expected_value, current_value)))
|
||||
|
||||
if discrepancies:
|
||||
status = "ERROR" if (category == "basic" or (is_main_domain and category != "extra")) else "WARNING"
|
||||
summary = ("diagnosis_dns_bad_conf", {"domain": domain, "category": category})
|
||||
else:
|
||||
status = "SUCCESS"
|
||||
summary = ("diagnosis_dns_good_conf", {"domain": domain, "category": category})
|
||||
|
||||
output = dict(meta={"domain": domain, "category": category},
|
||||
status=status,
|
||||
summary=summary)
|
||||
|
||||
if discrepancies:
|
||||
output["details"] = discrepancies
|
||||
|
||||
yield output
|
||||
|
||||
def get_current_record(self, domain, name, type_):
|
||||
if name == "@":
|
||||
command = "dig +short @%s %s %s" % (self.resolver, type_, domain)
|
||||
else:
|
||||
command = "dig +short @%s %s %s.%s" % (self.resolver, type_, name, domain)
|
||||
# FIXME : gotta handle case where this command fails ...
|
||||
# e.g. no internet connectivity (dependency mechanism to good result from 'ip' diagosis ?)
|
||||
# or the resolver is unavailable for some reason
|
||||
output = check_output(command).strip()
|
||||
if output.startswith('"') and output.endswith('"'):
|
||||
output = '"' + ' '.join(output.replace('"', ' ').split()) + '"'
|
||||
return output
|
||||
|
||||
|
||||
def main(args, env, loggers):
|
||||
return DNSRecordsDiagnoser(args, env, loggers).diagnose()
|
52
data/hooks/diagnosis/14-ports.py
Normal file
52
data/hooks/diagnosis/14-ports.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import requests
|
||||
|
||||
from yunohost.diagnosis import Diagnoser
|
||||
from yunohost.utils.error import YunohostError
|
||||
|
||||
|
||||
class PortsDiagnoser(Diagnoser):
|
||||
|
||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||
cache_duration = 3600
|
||||
dependencies = ["ip"]
|
||||
|
||||
def run(self):
|
||||
|
||||
# FIXME / TODO : in the future, maybe we want to report different
|
||||
# things per port depending on how important they are
|
||||
# (e.g. XMPP sounds to me much less important than other ports)
|
||||
# Ideally, a port could be related to a service...
|
||||
# FIXME / TODO : for now this list of port is hardcoded, might want
|
||||
# to fetch this from the firewall.yml in /etc/yunohost/
|
||||
ports = [22, 25, 53, 80, 443, 587, 993, 5222, 5269]
|
||||
|
||||
try:
|
||||
r = requests.post('https://ynhdiagnoser.netlib.re/check-ports', json={'ports': ports}, timeout=30).json()
|
||||
if "status" not in r.keys():
|
||||
raise Exception("Bad syntax for response ? Raw json: %s" % str(r))
|
||||
elif r["status"] == "error":
|
||||
if "content" in r.keys():
|
||||
raise Exception(r["content"])
|
||||
else:
|
||||
raise Exception("Bad syntax for response ? Raw json: %s" % str(r))
|
||||
elif r["status"] != "ok" or "ports" not in r.keys() or not isinstance(r["ports"], dict):
|
||||
raise Exception("Bad syntax for response ? Raw json: %s" % str(r))
|
||||
except Exception as e:
|
||||
raise YunohostError("diagnosis_ports_could_not_diagnose", error=e)
|
||||
|
||||
for port in ports:
|
||||
if r["ports"].get(str(port), None) is not True:
|
||||
yield dict(meta={"port": port},
|
||||
status="ERROR",
|
||||
summary=("diagnosis_ports_unreachable", {"port": port}))
|
||||
else:
|
||||
yield dict(meta={},
|
||||
status="SUCCESS",
|
||||
summary=("diagnosis_ports_ok", {"port": port}))
|
||||
|
||||
|
||||
def main(args, env, loggers):
|
||||
return PortsDiagnoser(args, env, loggers).diagnose()
|
57
data/hooks/diagnosis/16-http.py
Normal file
57
data/hooks/diagnosis/16-http.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import random
|
||||
import requests
|
||||
|
||||
from yunohost.diagnosis import Diagnoser
|
||||
from yunohost.domain import domain_list
|
||||
from yunohost.utils.error import YunohostError
|
||||
|
||||
|
||||
class HttpDiagnoser(Diagnoser):
|
||||
|
||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||
cache_duration = 3600
|
||||
dependencies = ["ip"]
|
||||
|
||||
def run(self):
|
||||
|
||||
nonce_digits = "0123456789abcedf"
|
||||
|
||||
all_domains = domain_list()["domains"]
|
||||
for domain in all_domains:
|
||||
|
||||
nonce = ''.join(random.choice(nonce_digits) for i in range(16))
|
||||
os.system("rm -rf /tmp/.well-known/ynh-diagnosis/")
|
||||
os.system("mkdir -p /tmp/.well-known/ynh-diagnosis/")
|
||||
os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % nonce)
|
||||
|
||||
try:
|
||||
r = requests.post('https://ynhdiagnoser.netlib.re/check-http', json={'domain': domain, "nonce": nonce}, timeout=30).json()
|
||||
if "status" not in r.keys():
|
||||
raise Exception("Bad syntax for response ? Raw json: %s" % str(r))
|
||||
elif r["status"] == "error" and ("code" not in r.keys() or r["code"] not in ["error_http_check_connection_error", "error_http_check_unknown_error"]):
|
||||
if "content" in r.keys():
|
||||
raise Exception(r["content"])
|
||||
else:
|
||||
raise Exception("Bad syntax for response ? Raw json: %s" % str(r))
|
||||
except Exception as e:
|
||||
raise YunohostError("diagnosis_http_could_not_diagnose", error=e)
|
||||
|
||||
if r["status"] == "ok":
|
||||
yield dict(meta={"domain": domain},
|
||||
status="SUCCESS",
|
||||
summary=("diagnosis_http_ok", {"domain": domain}))
|
||||
else:
|
||||
yield dict(meta={"domain": domain},
|
||||
status="ERROR",
|
||||
summary=("diagnosis_http_unreachable", {"domain": domain}))
|
||||
|
||||
# In there or idk where else ...
|
||||
# try to diagnose hairpinning situation by crafting a request for the
|
||||
# global ip (from within local network) and seeing if we're getting the right page ?
|
||||
|
||||
|
||||
def main(args, env, loggers):
|
||||
return HttpDiagnoser(args, env, loggers).diagnose()
|
42
data/hooks/diagnosis/18-mail.py
Normal file
42
data/hooks/diagnosis/18-mail.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
|
||||
from yunohost.diagnosis import Diagnoser
|
||||
|
||||
|
||||
class MailDiagnoser(Diagnoser):
|
||||
|
||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||
cache_duration = 3600
|
||||
dependencies = ["ip"]
|
||||
|
||||
def run(self):
|
||||
|
||||
# Is outgoing port 25 filtered somehow ?
|
||||
if os.system('/bin/nc -z -w2 yunohost.org 25') == 0:
|
||||
yield dict(meta={"test": "ougoing_port_25"},
|
||||
status="SUCCESS",
|
||||
summary=("diagnosis_mail_ougoing_port_25_ok",{}))
|
||||
else:
|
||||
yield dict(meta={"test": "outgoing_port_25"},
|
||||
status="ERROR",
|
||||
summary=("diagnosis_mail_ougoing_port_25_blocked",{}))
|
||||
|
||||
|
||||
|
||||
# Mail blacklist using dig requests (c.f. ljf's code)
|
||||
|
||||
# SMTP reachability (c.f. check-smtp to be implemented on yunohost's remote diagnoser)
|
||||
|
||||
# ideally, SPF / DMARC / DKIM validation ... (c.f. https://github.com/alexAubin/yunoScripts/blob/master/yunoDKIM.py possibly though that looks horrible)
|
||||
|
||||
# check that the mail queue is not filled with hundreds of email pending
|
||||
|
||||
# check that the recent mail logs are not filled with thousand of email sending (unusual number of mail sent)
|
||||
|
||||
# check for unusual failed sending attempt being refused in the logs ?
|
||||
|
||||
|
||||
def main(args, env, loggers):
|
||||
return MailDiagnoser(args, env, loggers).diagnose()
|
51
data/hooks/diagnosis/30-services.py
Normal file
51
data/hooks/diagnosis/30-services.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
|
||||
from yunohost.diagnosis import Diagnoser
|
||||
from yunohost.service import service_status
|
||||
|
||||
# TODO : all these are arbitrary, should be collectively validated
|
||||
services_ignored = {"glances"}
|
||||
services_critical = {"dnsmasq", "fail2ban", "yunohost-firewall", "nginx", "slapd", "ssh"}
|
||||
# TODO / FIXME : we should do something about this postfix thing
|
||||
# The nominal value is to be "exited" ... some daemon is actually running
|
||||
# in a different thread that the thing started by systemd, which is fine
|
||||
# but somehow sometimes it gets killed and there's no easy way to detect it
|
||||
# Just randomly restarting it will fix ths issue. We should find some trick
|
||||
# to identify the PID of the process and check it's still up or idk
|
||||
services_expected_to_be_exited = {"postfix", "yunohost-firewall"}
|
||||
|
||||
class ServicesDiagnoser(Diagnoser):
|
||||
|
||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||
cache_duration = 300
|
||||
dependencies = []
|
||||
|
||||
def run(self):
|
||||
|
||||
all_result = service_status()
|
||||
|
||||
for service, result in sorted(all_result.items()):
|
||||
|
||||
if service in services_ignored:
|
||||
continue
|
||||
|
||||
item = dict(meta={"service": service})
|
||||
expected_status = "running" if service not in services_expected_to_be_exited else "exited"
|
||||
|
||||
# TODO / FIXME : might also want to check that services are enabled
|
||||
|
||||
if result["active"] != "active" or result["status"] != expected_status:
|
||||
item["status"] = "WARNING" if service not in services_critical else "ERROR"
|
||||
item["summary"] = ("diagnosis_services_bad_status", {"service": service, "status": result["active"] + "/" + result["status"]})
|
||||
|
||||
# TODO : could try to append the tail of the service log to the "details" key ...
|
||||
else:
|
||||
item["status"] = "SUCCESS"
|
||||
item["summary"] = ("diagnosis_services_good_status", {"service": service, "status": result["active"] + "/" + result["status"]})
|
||||
|
||||
yield item
|
||||
|
||||
def main(args, env, loggers):
|
||||
return ServicesDiagnoser(args, env, loggers).diagnose()
|
85
data/hooks/diagnosis/50-systemresources.py
Normal file
85
data/hooks/diagnosis/50-systemresources.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import psutil
|
||||
|
||||
from yunohost.diagnosis import Diagnoser
|
||||
|
||||
class SystemResourcesDiagnoser(Diagnoser):
|
||||
|
||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||
cache_duration = 3600 * 24
|
||||
dependencies = []
|
||||
|
||||
def run(self):
|
||||
|
||||
#
|
||||
# RAM
|
||||
#
|
||||
|
||||
ram = psutil.virtual_memory()
|
||||
ram_total_abs_MB = ram.total / (1024**2)
|
||||
ram_available_abs_MB = ram.available / (1024**2)
|
||||
ram_available_percent = round(100 * ram.available / ram.total)
|
||||
item = dict(meta={"test": "ram"})
|
||||
infos = {"total_abs_MB": ram_total_abs_MB, "available_abs_MB": ram_available_abs_MB, "available_percent": ram_available_percent}
|
||||
if ram_available_abs_MB < 100 or ram_available_percent < 5:
|
||||
item["status"] = "ERROR"
|
||||
item["summary"] = ("diagnosis_ram_verylow", infos)
|
||||
elif ram_available_abs_MB < 200 or ram_available_percent < 10:
|
||||
item["status"] = "WARNING"
|
||||
item["summary"] = ("diagnosis_ram_low", infos)
|
||||
else:
|
||||
item["status"] = "SUCCESS"
|
||||
item["summary"] = ("diagnosis_ram_ok", infos)
|
||||
yield item
|
||||
|
||||
#
|
||||
# Swap
|
||||
#
|
||||
|
||||
swap = psutil.swap_memory()
|
||||
swap_total_abs_MB = swap.total / (1024*1024)
|
||||
item = dict(meta={"test": "swap"})
|
||||
infos = {"total_MB": swap_total_abs_MB}
|
||||
if swap_total_abs_MB <= 0:
|
||||
item["status"] = "ERROR"
|
||||
item["summary"] = ("diagnosis_swap_none", infos)
|
||||
elif swap_total_abs_MB <= 256:
|
||||
item["status"] = "WARNING"
|
||||
item["summary"] = ("diagnosis_swap_notsomuch", infos)
|
||||
else:
|
||||
item["status"] = "SUCCESS"
|
||||
item["summary"] = ("diagnosis_swap_ok", infos)
|
||||
yield item
|
||||
|
||||
#
|
||||
# Disks usage
|
||||
#
|
||||
|
||||
disk_partitions = psutil.disk_partitions()
|
||||
|
||||
for disk_partition in disk_partitions:
|
||||
device = disk_partition.device
|
||||
mountpoint = disk_partition.mountpoint
|
||||
|
||||
usage = psutil.disk_usage(mountpoint)
|
||||
free_abs_GB = usage.free / (1024 ** 3)
|
||||
free_percent = 100 - usage.percent
|
||||
|
||||
item = dict(meta={"test": "diskusage", "mountpoint": mountpoint})
|
||||
infos = {"mountpoint": mountpoint, "device": device, "free_abs_GB": free_abs_GB, "free_percent": free_percent}
|
||||
if free_abs_GB < 1 or free_percent < 5:
|
||||
item["status"] = "ERROR"
|
||||
item["summary"] = ("diagnosis_diskusage_verylow", infos)
|
||||
elif free_abs_GB < 2 or free_percent < 10:
|
||||
item["status"] = "WARNING"
|
||||
item["summary"] = ("diagnosis_diskusage_low", infos)
|
||||
else:
|
||||
item["status"] = "SUCCESS"
|
||||
item["summary"] = ("diagnosis_diskusage_ok", infos)
|
||||
|
||||
yield item
|
||||
|
||||
|
||||
def main(args, env, loggers):
|
||||
return SystemResourcesDiagnoser(args, env, loggers).diagnose()
|
56
data/hooks/diagnosis/70-regenconf.py
Normal file
56
data/hooks/diagnosis/70-regenconf.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
|
||||
import subprocess
|
||||
from yunohost.diagnosis import Diagnoser
|
||||
from yunohost.regenconf import manually_modified_files, manually_modified_files_compared_to_debian_default
|
||||
|
||||
|
||||
class RegenconfDiagnoser(Diagnoser):
|
||||
|
||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||
cache_duration = 300
|
||||
dependencies = []
|
||||
|
||||
def run(self):
|
||||
|
||||
# nginx -t
|
||||
p = subprocess.Popen("nginx -t".split(),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
out, _ = p.communicate()
|
||||
|
||||
if p.returncode != 0:
|
||||
yield dict(meta={"test": "nginx-t"},
|
||||
status="ERROR",
|
||||
summary=("diagnosis_regenconf_nginx_conf_broken", {}),
|
||||
details=[(out, ())]
|
||||
)
|
||||
|
||||
regenconf_modified_files = manually_modified_files()
|
||||
debian_modified_files = manually_modified_files_compared_to_debian_default(ignore_handled_by_regenconf=True)
|
||||
|
||||
if regenconf_modified_files == []:
|
||||
yield dict(meta={"test": "regenconf"},
|
||||
status="SUCCESS",
|
||||
summary=("diagnosis_regenconf_allgood", {})
|
||||
)
|
||||
else:
|
||||
for f in regenconf_modified_files:
|
||||
yield dict(meta={"test": "regenconf", "file": f},
|
||||
status="WARNING",
|
||||
summary=("diagnosis_regenconf_manually_modified", {"file": f}),
|
||||
details=[("diagnosis_regenconf_manually_modified_details", {})]
|
||||
)
|
||||
|
||||
for f in debian_modified_files:
|
||||
yield dict(meta={"test": "debian", "file": f},
|
||||
status="WARNING",
|
||||
summary=("diagnosis_regenconf_manually_modified_debian", {"file": f}),
|
||||
details=[("diagnosis_regenconf_manually_modified_debian_details", {})]
|
||||
)
|
||||
|
||||
|
||||
def main(args, env, loggers):
|
||||
return RegenconfDiagnoser(args, env, loggers).diagnose()
|
98
data/hooks/diagnosis/90-security.py
Normal file
98
data/hooks/diagnosis/90-security.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
from yunohost.diagnosis import Diagnoser
|
||||
from moulinette.utils.filesystem import read_json, write_to_json
|
||||
|
||||
|
||||
class SecurityDiagnoser(Diagnoser):
|
||||
|
||||
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
|
||||
cache_duration = 3600
|
||||
dependencies = []
|
||||
|
||||
def run(self):
|
||||
|
||||
"CVE-2017-5754"
|
||||
|
||||
if self.is_vulnerable_to_meltdown():
|
||||
yield dict(meta={"test": "meltdown"},
|
||||
status="ERROR",
|
||||
summary=("diagnosis_security_vulnerable_to_meltdown", {}),
|
||||
details=[("diagnosis_security_vulnerable_to_meltdown_details", ())]
|
||||
)
|
||||
else:
|
||||
yield dict(meta={},
|
||||
status="SUCCESS",
|
||||
summary=("diagnosis_security_all_good", {})
|
||||
)
|
||||
|
||||
|
||||
def is_vulnerable_to_meltdown(self):
|
||||
# meltdown CVE: https://security-tracker.debian.org/tracker/CVE-2017-5754
|
||||
|
||||
# We use a cache file to avoid re-running the script so many times,
|
||||
# which can be expensive (up to around 5 seconds on ARM)
|
||||
# and make the admin appear to be slow (c.f. the calls to diagnosis
|
||||
# from the webadmin)
|
||||
#
|
||||
# The cache is in /tmp and shall disappear upon reboot
|
||||
# *or* we compare it to dpkg.log modification time
|
||||
# such that it's re-ran if there was package upgrades
|
||||
# (e.g. from yunohost)
|
||||
cache_file = "/tmp/yunohost-meltdown-diagnosis"
|
||||
dpkg_log = "/var/log/dpkg.log"
|
||||
if os.path.exists(cache_file):
|
||||
if not os.path.exists(dpkg_log) or os.path.getmtime(cache_file) > os.path.getmtime(dpkg_log):
|
||||
self.logger_debug("Using cached results for meltdown checker, from %s" % cache_file)
|
||||
return read_json(cache_file)[0]["VULNERABLE"]
|
||||
|
||||
# script taken from https://github.com/speed47/spectre-meltdown-checker
|
||||
# script commit id is store directly in the script
|
||||
SCRIPT_PATH = "/usr/lib/moulinette/yunohost/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh"
|
||||
|
||||
# '--variant 3' corresponds to Meltdown
|
||||
# example output from the script:
|
||||
# [{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}]
|
||||
try:
|
||||
self.logger_debug("Running meltdown vulnerability checker")
|
||||
call = subprocess.Popen("bash %s --batch json --variant 3" %
|
||||
SCRIPT_PATH, shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
|
||||
# TODO / FIXME : here we are ignoring error messages ...
|
||||
# in particular on RPi2 and other hardware, the script complains about
|
||||
# "missing some kernel info (see -v), accuracy might be reduced"
|
||||
# Dunno what to do about that but we probably don't want to harass
|
||||
# users with this warning ...
|
||||
output, err = call.communicate()
|
||||
assert call.returncode in (0, 2, 3), "Return code: %s" % call.returncode
|
||||
|
||||
# If there are multiple lines, sounds like there was some messages
|
||||
# in stdout that are not json >.> ... Try to get the actual json
|
||||
# stuff which should be the last line
|
||||
output = output.strip()
|
||||
if "\n" in output:
|
||||
self.logger_debug("Original meltdown checker output : %s" % output)
|
||||
output = output.split("\n")[-1]
|
||||
|
||||
CVEs = json.loads(output)
|
||||
assert len(CVEs) == 1
|
||||
assert CVEs[0]["NAME"] == "MELTDOWN"
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.logger_warning("Something wrong happened when trying to diagnose Meltdown vunerability, exception: %s" % e)
|
||||
raise Exception("Command output for failed meltdown check: '%s'" % output)
|
||||
|
||||
self.logger_debug("Writing results from meltdown checker to cache file, %s" % cache_file)
|
||||
write_to_json(cache_file, CVEs)
|
||||
return CVEs[0]["VULNERABLE"]
|
||||
|
||||
|
||||
def main(args, env, loggers):
|
||||
return SecurityDiagnoser(args, env, loggers).diagnose()
|
|
@ -3,6 +3,5 @@ backup_dir="$1/conf/ynh/certs"
|
|||
sudo mkdir -p /etc/yunohost/certs/
|
||||
|
||||
sudo cp -a $backup_dir/. /etc/yunohost/certs/
|
||||
sudo yunohost app ssowatconf
|
||||
sudo service nginx reload
|
||||
sudo service metronome reload
|
||||
|
|
|
@ -57,18 +57,24 @@ children:
|
|||
objectClass:
|
||||
- posixGroup
|
||||
- groupOfNamesYnh
|
||||
cn=visitors,ou=groups:
|
||||
cn: visitors
|
||||
gidNumber: "4003"
|
||||
objectClass:
|
||||
- posixGroup
|
||||
- groupOfNamesYnh
|
||||
|
||||
depends_children:
|
||||
cn=main.mail,ou=permission:
|
||||
cn: main.mail
|
||||
cn=mail.main,ou=permission:
|
||||
cn: mail.main
|
||||
gidNumber: "5001"
|
||||
objectClass:
|
||||
- posixGroup
|
||||
- permissionYnh
|
||||
groupPermission:
|
||||
- "cn=all_users,ou=groups,dc=yunohost,dc=org"
|
||||
cn=main.metronome,ou=permission:
|
||||
cn: main.metronome
|
||||
cn=xmpp.main,ou=permission:
|
||||
cn: xmpp.main
|
||||
gidNumber: "5002"
|
||||
objectClass:
|
||||
- posixGroup
|
||||
|
|
|
@ -3,7 +3,7 @@ auth_bind = yes
|
|||
ldap_version = 3
|
||||
base = ou=users,dc=yunohost,dc=org
|
||||
user_attrs = uidNumber=500,gidNumber=8,mailuserquota=quota_rule=*:bytes=%$
|
||||
user_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org))
|
||||
pass_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org))
|
||||
user_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org))
|
||||
pass_filter = (&(objectClass=inetOrgPerson)(uid=%n)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org))
|
||||
default_pass_scheme = SSHA
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ bantime = 600
|
|||
findtime = 600
|
||||
|
||||
# "maxretry" is the number of failures before a host get banned.
|
||||
maxretry = 5
|
||||
maxretry = 10
|
||||
|
||||
# "backend" specifies the backend used to get files modification.
|
||||
# Available options are "pyinotify", "gamin", "polling", "systemd" and "auto".
|
||||
|
|
|
@ -29,4 +29,3 @@ protocol = tcp
|
|||
filter = yunohost
|
||||
logpath = /var/log/nginx/*error.log
|
||||
/var/log/nginx/*access.log
|
||||
maxretry = 6
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
# Default is to launch glances with '-s' option.
|
||||
DAEMON_ARGS="-s -B 127.0.0.1"
|
||||
|
||||
# Change to 'true' to have glances running at startup
|
||||
RUN="true"
|
|
@ -8,7 +8,7 @@ VirtualHost "{{ domain }}"
|
|||
hostname = "localhost",
|
||||
user = {
|
||||
basedn = "ou=users,dc=yunohost,dc=org",
|
||||
filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=main.metronome,ou=permission,dc=yunohost,dc=org))",
|
||||
filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=xmpp.main,ou=permission,dc=yunohost,dc=org))",
|
||||
usernamefield = "mail",
|
||||
namefield = "cn",
|
||||
},
|
||||
|
|
|
@ -16,6 +16,10 @@ server {
|
|||
return 301 https://$http_host$request_uri;
|
||||
}
|
||||
|
||||
location /.well-known/ynh-diagnosis/ {
|
||||
alias /tmp/.well-known/ynh-diagnosis/;
|
||||
}
|
||||
|
||||
location /.well-known/autoconfig/mail/ {
|
||||
alias /var/www/.well-known/{{ domain }}/autoconfig/mail/;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
server_host = localhost
|
||||
server_port = 389
|
||||
search_base = dc=yunohost,dc=org
|
||||
query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org))
|
||||
query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org))
|
||||
result_attribute = uid
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
server_host = localhost
|
||||
server_port = 389
|
||||
search_base = dc=yunohost,dc=org
|
||||
query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=main.mail,ou=permission,dc=yunohost,dc=org))
|
||||
query_filter = (&(objectClass=mailAccount)(mail=%s)(permission=cn=mail.main,ou=permission,dc=yunohost,dc=org))
|
||||
result_attribute = maildrop
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
# The VCS to use.
|
||||
#VCS="hg"
|
||||
VCS="git"
|
||||
#VCS="bzr"
|
||||
#VCS="darcs"
|
||||
|
||||
# Options passed to git commit when run by etckeeper.
|
||||
GIT_COMMIT_OPTIONS="--quiet"
|
||||
|
||||
# Options passed to hg commit when run by etckeeper.
|
||||
HG_COMMIT_OPTIONS=""
|
||||
|
||||
# Options passed to bzr commit when run by etckeeper.
|
||||
BZR_COMMIT_OPTIONS=""
|
||||
|
||||
# Options passed to darcs record when run by etckeeper.
|
||||
DARCS_COMMIT_OPTIONS="-a"
|
||||
|
||||
# Uncomment to avoid etckeeper committing existing changes
|
||||
# to /etc automatically once per day.
|
||||
#AVOID_DAILY_AUTOCOMMITS=1
|
||||
|
||||
# Uncomment the following to avoid special file warning
|
||||
# (the option is enabled automatically by cronjob regardless).
|
||||
#AVOID_SPECIAL_FILE_WARNING=1
|
||||
|
||||
# Uncomment to avoid etckeeper committing existing changes to
|
||||
# /etc before installation. It will cancel the installation,
|
||||
# so you can commit the changes by hand.
|
||||
#AVOID_COMMIT_BEFORE_INSTALL=1
|
||||
|
||||
# The high-level package manager that's being used.
|
||||
# (apt, pacman-g2, yum, zypper etc)
|
||||
HIGHLEVEL_PACKAGE_MANAGER=apt
|
||||
|
||||
# The low-level package manager that's being used.
|
||||
# (dpkg, rpm, pacman, pacman-g2, etc)
|
||||
LOWLEVEL_PACKAGE_MANAGER=dpkg
|
||||
|
||||
# To push each commit to a remote, put the name of the remote here.
|
||||
# (eg, "origin" for git). Space-separated lists of multiple remotes
|
||||
# also work (eg, "origin gitlab github" for git).
|
||||
PUSH_REMOTE=""
|
|
@ -17,7 +17,6 @@ redis-server:
|
|||
mysql:
|
||||
log: [/var/log/mysql.log,/var/log/mysql.err]
|
||||
alternates: ['mariadb']
|
||||
glances: {}
|
||||
ssh:
|
||||
log: /var/log/auth.log
|
||||
metronome:
|
||||
|
@ -32,6 +31,7 @@ yunohost-firewall:
|
|||
need_lock: true
|
||||
nslcd:
|
||||
log: /var/log/syslog
|
||||
glances: null
|
||||
nsswitch: null
|
||||
ssl: null
|
||||
yunohost: null
|
||||
|
|
76
debian/changelog
vendored
76
debian/changelog
vendored
|
@ -1,3 +1,79 @@
|
|||
yunohost (3.7.0.1) testing; urgency=low
|
||||
|
||||
- Hotfix to avoid having a shitload of warnings displayed during the permission migration
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 31 Oct 2019 20:35:00 +0000
|
||||
|
||||
yunohost (3.7.0) testing; urgency=low
|
||||
|
||||
# ~ Major stuff
|
||||
|
||||
- [enh] Add group and permission mechanism (YunoHost#585, YunoHost#763, YunoHost#789, YunoHost#790, YunoHost#795, YunoHost#797, SSOwat#147, Moulinette#189, YunoHost-admin#257)
|
||||
- [mod] Rework migration system to have independent migrations (YunoHost#768, YunoHost#774, YunoHost-admin#258)
|
||||
- [enh] Many improvements in the way app action failures are handled (YunoHost#769, YunoHost#811)
|
||||
- [enh] Improve checks for system anomalies after app operations (YunoHost#785)
|
||||
- [mod] Spookier warnings for dangerous app installs (YunoHost#814, Moulinette/808f620)
|
||||
- [enh] Support app manifests in toml (YunoHost#748, Moulinette#204, Moulinette/55515cb)
|
||||
- [mod] Get rid of etckeeper (YunoHost#803)
|
||||
- [enh] Quite a lot of messages improvements, string cleaning, language rework... (YunoHost#793, YunoHost#799, YunoHost#823, SSOwat#143, YunoHost#766, YunoHost#767, YunoHost/fd99ef0, YunoHost/92a6315, YunoHost-admin/10ea04a, Moulinette/599bec3, Moulinette#208, Moulinette#213, Moulinette/b7d415d, Moulinette/a8966b8, Moulinette/fdf9a71, Moulinette/d895ae3, Moulinette/bdf0a1c, YunoHost#817, YunoHost#823, YunoHost/79627d7, YunoHost/9ee3d23, YunoHost-admin#265)
|
||||
- [i18n] Improved translations for Catalan, Occitan, French, Esperanto, Arabic, German, Spanish, Norwegian Bokmål, Portuguese
|
||||
|
||||
# Smaller or pretty technical fix/enh
|
||||
|
||||
- [enh] Add unit/functional tests for apps + improve other tests (YunoHost#779, YunoHost#808)
|
||||
- [enh] Preparations for moulinette Python3 migration (Tox, Pytest and unit tests) (Moulinette#203, Moulinette#206, Moulinette#207, Moulinette#210, Moulinette#211 Moulinette#212, Moulinette/2403ee1, Moulinette/69b0d49, Moulinette/49c749c, Moulinette/2c84ee1, Moulinette/cef72f7, YunoHost/6365a26)
|
||||
- [enh] Support python hooks (YunoHost#747)
|
||||
- [enh] Upgrade n version + compatibility with arm64 (YunoHost#753)
|
||||
- [enh] Add OpenLDAP TLS support (YunoHost#755, YunoHost/0a2d1c7, YunoHost/2dc8095)
|
||||
- [enh] Improve PostgreSQL password security (YunoHost#762)
|
||||
- [enh] Integrate actions/config-panel into operation logs (YunoHost#764)
|
||||
- [mod] Assume that apps without any 'path' setting defined aren't webapps (YunoHost#765)
|
||||
- [fix] Set dpkg vendor to YunoHost (YunoHost#749, YunoHost#772)
|
||||
- [enh] Adding variable 'token' to data to redact from logs (YunoHost#783)
|
||||
- [enh] Add --force and --dry-run options to 'yunohost dyndns update' (YunoHost#786)
|
||||
- [fix] Don't throw a fatal error if we can't change the hostname (YunoHost/fe3ecd7)
|
||||
- [enh] Dynamically evaluate proper mariadb-server-<version> (YunoHost/f0440fb)
|
||||
- [fix] Bad format for backup info.json ... (YunoHost/7d0119a)
|
||||
- [fix] Inline buttons responsiveness on migration screen (YunoHost-admin#259)
|
||||
- [enh] Add debug logs to SSOwat (SSOwat#145)
|
||||
- [enh] Add a write_to_yaml utility similar to write_to_json (Moulinette/2e2e627)
|
||||
- [enh] Warn the user about long locks (Moulinette#205)
|
||||
- [mod] Tweak stuff about setuptools and moulinette deps? (Moulinette/b739f27, Moulinette/da00fc9, Moulinette/d8cbbb0)
|
||||
- [fix] Misc micro bugfixes or improvements (YunoHost#743, YunoHost#792, YunoHost/6f48d1d, YunoHost/d516cf8, YunoHost#819, Moulinette/83d9e77, YunoHost/63d364e, YunoHost/68e9724, YunoHost/0849adb, YunoHost/19dbe87, YunoHost/61931f2, YunoHost/6dc720f, YunoHost/4def4df, SSOwat#140, SSOwat#141, YunoHost#829)
|
||||
- [doc] Fix doc building + add doc build tests with Tox (Moulinette/f1ac5b8, Moulinette/df7d478, Moulinette/74c8f79, Moulinette/bcf92c7, Moulinette/af2c80c, Moulinette/d52a574, Moulinette/307f660, Moulinette/dced104, Moulinette/ed3823b)
|
||||
- [enh] READMEs improvements (YunoHost/b3398e7, SSOwat/ee67b6f, Moulinette/1541b74, Moulinette/ad1eeef, YunoHost/25afdd4, YunoHost/73741f6)
|
||||
|
||||
Thanks to all contributors <3 ! (accross all repo: Yunohost, Moulinette, SSOwat, Yunohost-admin) : advocatux, Aksel K., Aleks, Allan N., amirale qt, Armin P., Bram, ButterflyOfFire, Carles S. A., chema o. r., decentral1se, Emmanuel V., Etienne M., Filip B., Geoff M., htsr, Jibec, Josué, Julien J., Kayou, liberodark, ljf, lucaskev, Lukas D., madtibo, Martin D., Mélanie C., nr 458 h, pitfd, ppr, Quentí, sidddy, troll, tufek yamero, xaloc33, yalh76
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 31 Oct 2019 18:00:00 +0000
|
||||
|
||||
yunohost (3.6.5.3) stable; urgency=low
|
||||
|
||||
- [fix] More general grep for the php/sury dependency nightmare fix (followup of #809)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Tue, 29 Oct 2019 03:48:00 +0000
|
||||
|
||||
yunohost (3.6.5.2) stable; urgency=low
|
||||
|
||||
- [fix] Alex was drunk and released an epic stupid bug in stable (2623d385)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 10 Oct 2019 01:00:00 +0000
|
||||
|
||||
yunohost (3.6.5.1) stable; urgency=low
|
||||
|
||||
- [mod] Change maxretry of fail2ban from 6 to 10 (fe8fd1b)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Tue, 08 Oct 2019 20:00:00 +0000
|
||||
|
||||
yunohost (3.6.5) stable; urgency=low
|
||||
|
||||
- [enh] Detect and warn early about unavailable full domains... (#798)
|
||||
- [mod] Change maxretry of fail2ban from 6 to 10 (#802)
|
||||
- [fix] Epicly ugly workaround for the goddamn dependency nighmare about sury fucking up php7.0 dependencies (#809)
|
||||
- [fix] Support logfiles not ending with .log in logrotate ... (#810)
|
||||
|
||||
-- Alexandre Aubin <alex.aubin@mailoo.org> Tue, 08 Oct 2019 19:00:00 +0000
|
||||
|
||||
yunohost (3.6.4.6) stable; urgency=low
|
||||
|
||||
- [fix] Hopefully fix the issue about corrupted logs metadata files (d507d447, 1cec9d78)
|
||||
|
|
6
debian/control
vendored
6
debian/control
vendored
|
@ -15,9 +15,9 @@ Depends: ${python:Depends}, ${misc:Depends}
|
|||
, python-psutil, python-requests, python-dnspython, python-openssl
|
||||
, python-apt, python-miniupnpc, python-dbus, python-jinja2
|
||||
, python-toml
|
||||
, glances, apt-transport-https
|
||||
, apt-transport-https
|
||||
, dnsutils, bind9utils, unzip, git, curl, cron, wget, jq
|
||||
, ca-certificates, netcat-openbsd, iproute
|
||||
, ca-certificates, netcat-openbsd, iproute2
|
||||
, mariadb-server, php-mysql | php-mysqlnd
|
||||
, slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd
|
||||
, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail, mailutils, postsrsd
|
||||
|
@ -31,7 +31,7 @@ Depends: ${python:Depends}, ${misc:Depends}
|
|||
, equivs, lsof
|
||||
Recommends: yunohost-admin
|
||||
, openssh-server, ntp, inetutils-ping | iputils-ping
|
||||
, bash-completion, rsyslog, etckeeper
|
||||
, bash-completion, rsyslog
|
||||
, php-gd, php-curl, php-gettext, php-mcrypt
|
||||
, python-pip
|
||||
, unattended-upgrades
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
"app_location_install_failed": "Unable to install the app in this location because it conflit with the app '{other_app}' already installed on '{other_path}'",
|
||||
"app_location_unavailable": "This url is not available or conflicts with an already installed app",
|
||||
"app_manifest_invalid": "Invalid app manifest: {error}",
|
||||
"app_no_upgrade": "البرمجيات لا تحتاج إلى تحديث",
|
||||
"app_no_upgrade": "ليس هناك أي تطبيق بحاجة إلى تحديث",
|
||||
"app_not_correctly_installed": "يبدو أن التطبيق {app:s} لم يتم تنصيبه بشكل صحيح",
|
||||
"app_not_installed": "إنّ التطبيق {app:s} غير مُنصَّب",
|
||||
"app_not_properly_removed": "لم يتم حذف تطبيق {app:s} بشكلٍ جيّد",
|
||||
|
@ -37,17 +37,17 @@
|
|||
"app_unsupported_remote_type": "Unsupported remote type used for the app",
|
||||
"app_upgrade_app_name": "جارٍ تحديث تطبيق {app}…",
|
||||
"app_upgrade_failed": "تعذرت عملية ترقية {app:s}",
|
||||
"app_upgrade_some_app_failed": "تعذرت عملية ترقية بعض البرمجيات",
|
||||
"app_upgrade_some_app_failed": "تعذرت عملية ترقية بعض التطبيقات",
|
||||
"app_upgraded": "تم تحديث التطبيق {app:s}",
|
||||
"appslist_corrupted_json": "Could not load the application lists. It looks like {filename:s} is corrupted.",
|
||||
"appslist_could_not_migrate": "Could not migrate app list {appslist:s} ! Unable to parse the url... The old cron job has been kept in {bkp_file:s}.",
|
||||
"appslist_fetched": "تم جلب قائمة تطبيقات {appslist:s}",
|
||||
"appslist_migrating": "Migrating application list {appslist:s} …",
|
||||
"appslist_name_already_tracked": "There is already a registered application list with name {name:s}.",
|
||||
"appslist_removed": "تم حذف قائمة البرمجيات {appslist:s}",
|
||||
"appslist_removed": "تم حذف قائمة التطبيقات {appslist:s}",
|
||||
"appslist_retrieve_bad_format": "Retrieved file for application list {appslist:s} is not valid",
|
||||
"appslist_retrieve_error": "Unable to retrieve the remote application list {appslist:s}: {error:s}",
|
||||
"appslist_unknown": "قائمة البرمجيات {appslist:s} مجهولة.",
|
||||
"appslist_unknown": "قائمة التطبيقات {appslist:s} مجهولة.",
|
||||
"appslist_url_already_tracked": "There is already a registered application list with url {url:s}.",
|
||||
"ask_current_admin_password": "كلمة السر الإدارية الحالية",
|
||||
"ask_email": "عنوان البريد الإلكتروني",
|
||||
|
@ -114,7 +114,7 @@
|
|||
"certmanager_attempt_to_replace_valid_cert": "You are attempting to overwrite a good and valid certificate for domain {domain:s}! (Use --force to bypass)",
|
||||
"certmanager_cannot_read_cert": "Something wrong happened when trying to open current certificate for domain {domain:s} (file: {file:s}), reason: {reason:s}",
|
||||
"certmanager_cert_install_success": "تمت عملية تنصيب شهادة Let's Encrypt بنجاح على النطاق {domain:s} !",
|
||||
"certmanager_cert_install_success_selfsigned": "Successfully installed a self-signed certificate for domain {domain:s}!",
|
||||
"certmanager_cert_install_success_selfsigned": "نجحت عملية تثبيت الشهادة الموقعة ذاتيا الخاصة بالنطاق {domain:s}!",
|
||||
"certmanager_cert_renew_success": "نجحت عملية تجديد شهادة Let's Encrypt الخاصة باسم النطاق {domain:s} !",
|
||||
"certmanager_cert_signing_failed": "فشل إجراء توقيع الشهادة الجديدة",
|
||||
"certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow…",
|
||||
|
@ -211,8 +211,8 @@
|
|||
"mail_domain_unknown": "Unknown mail address domain '{domain:s}'",
|
||||
"mail_forward_remove_failed": "Unable to remove mail forward '{mail:s}'",
|
||||
"mailbox_used_space_dovecot_down": "Dovecot mailbox service need to be up, if you want to get mailbox used space",
|
||||
"maindomain_change_failed": "Unable to change the main domain",
|
||||
"maindomain_changed": "The main domain has been changed",
|
||||
"main_domain_change_failed": "Unable to change the main domain",
|
||||
"main_domain_changed": "The main domain has been changed",
|
||||
"migrate_tsig_end": "Migration to hmac-sha512 finished",
|
||||
"migrate_tsig_failed": "Migrating the dyndns domain {domain} to hmac-sha512 failed, rolling back. Error: {error_code} - {error}",
|
||||
"migrate_tsig_start": "Not secure enough key algorithm detected for TSIG signature of domain '{domain}', initiating migration to the more secure one hmac-sha512",
|
||||
|
@ -232,7 +232,7 @@
|
|||
"migrations_no_migrations_to_run": "No migrations to run",
|
||||
"migrations_show_currently_running_migration": "Running migration {number} {name}…",
|
||||
"migrations_show_last_migration": "Last ran migration is {}",
|
||||
"migrations_skip_migration": "جارٍ تجاهل التهجير {number} {name}…",
|
||||
"migrations_skip_migration": "جارٍ تجاهل التهجير {id}…",
|
||||
"monitor_disabled": "The server monitoring has been disabled",
|
||||
"monitor_enabled": "The server monitoring has been enabled",
|
||||
"monitor_glances_con_failed": "Unable to connect to Glances server",
|
||||
|
@ -301,7 +301,7 @@
|
|||
"service_add_failed": "تعذرت إضافة خدمة '{service:s}'",
|
||||
"service_added": "The service '{service:s}' has been added",
|
||||
"service_already_started": "Service '{service:s}' has already been started",
|
||||
"service_already_stopped": "Service '{service:s}' has already been stopped",
|
||||
"service_already_stopped": "إنّ خدمة '{service:s}' متوقفة مِن قبلُ",
|
||||
"service_cmd_exec_failed": "Unable to execute command '{command:s}'",
|
||||
"service_conf_file_backed_up": "The configuration file '{conf}' has been backed up to '{backup}'",
|
||||
"service_conf_file_copy_failed": "Unable to copy the new configuration file '{new}' to '{conf}'",
|
||||
|
@ -316,7 +316,7 @@
|
|||
"service_conf_updated": "The configuration has been updated for service '{service}'",
|
||||
"service_conf_would_be_updated": "The configuration would have been updated for service '{service}'",
|
||||
"service_disable_failed": "",
|
||||
"service_disabled": "The service '{service:s}' has been disabled",
|
||||
"service_disabled": "تم تعطيل خدمة '{service:s}'",
|
||||
"service_enable_failed": "",
|
||||
"service_enabled": "تم تنشيط خدمة '{service:s}'",
|
||||
"service_no_log": "ليس لخدمة '{service:s}' أي سِجلّ للعرض",
|
||||
|
@ -329,7 +329,7 @@
|
|||
"service_started": "تم إطلاق تشغيل خدمة '{service:s}'",
|
||||
"service_status_failed": "Unable to determine status of service '{service:s}'",
|
||||
"service_stop_failed": "",
|
||||
"service_stopped": "The service '{service:s}' has been stopped",
|
||||
"service_stopped": "تمّ إيقاف خدمة '{service:s}'",
|
||||
"service_unknown": "Unknown service '{service:s}'",
|
||||
"ssowat_conf_generated": "The SSOwat configuration has been generated",
|
||||
"ssowat_conf_updated": "The SSOwat configuration has been updated",
|
||||
|
@ -343,11 +343,11 @@
|
|||
"unlimit": "دون تحديد الحصة",
|
||||
"unrestore_app": "App '{app:s}' will not be restored",
|
||||
"update_cache_failed": "Unable to update APT cache",
|
||||
"updating_apt_cache": "جارٍ تحديث قائمة الحُزم المتوفرة …",
|
||||
"updating_apt_cache": "جارٍ جلب قائمة حُزم النظام المحدّثة المتوفرة…",
|
||||
"upgrade_complete": "إكتملت عملية الترقية و التحديث",
|
||||
"upgrading_packages": "عملية ترقية الحُزم جارية …",
|
||||
"upnp_dev_not_found": "No UPnP device found",
|
||||
"upnp_disabled": "UPnP has been disabled",
|
||||
"upnp_disabled": "تم تعطيل UPnP",
|
||||
"upnp_enabled": "UPnP has been enabled",
|
||||
"upnp_port_open_failed": "Unable to open UPnP ports",
|
||||
"user_created": "تم إنشاء المستخدم",
|
||||
|
@ -404,7 +404,7 @@
|
|||
"log_user_create": "إضافة المستخدم '{}'",
|
||||
"log_user_delete": "حذف المستخدم '{}'",
|
||||
"log_user_update": "تحديث معلومات المستخدم '{}'",
|
||||
"log_tools_maindomain": "جعل '{}' كنطاق أساسي",
|
||||
"log_domain_main_domain": "جعل '{}' كنطاق أساسي",
|
||||
"log_tools_upgrade": "تحديث حُزم ديبيان",
|
||||
"log_tools_shutdown": "إطفاء الخادم",
|
||||
"log_tools_reboot": "إعادة تشغيل الخادم",
|
||||
|
@ -412,7 +412,7 @@
|
|||
"service_description_dnsmasq": "مُكلَّف بتحليل أسماء النطاقات (DNS)",
|
||||
"service_description_mysql": "يقوم بتخزين بيانات التطبيقات (قواعد بيانات SQL)",
|
||||
"service_description_rspamd": "يقوم بتصفية البريد المزعج و إدارة ميزات أخرى للبريد",
|
||||
"service_description_yunohost-firewall": "يريد فتح و غلق منافذ الإتصال إلى الخدمات",
|
||||
"service_description_yunohost-firewall": "يُدير فتح وإغلاق منافذ الإتصال إلى الخدمات",
|
||||
"users_available": "المستخدمون المتوفرون:",
|
||||
"aborting": "إلغاء.",
|
||||
"admin_password_too_long": "يرجى اختيار كلمة سرية أقصر مِن 127 حرف",
|
||||
|
@ -426,5 +426,22 @@
|
|||
"global_settings_setting_security_password_admin_strength": "قوة الكلمة السرية الإدارية",
|
||||
"global_settings_setting_security_password_user_strength": "قوة الكلمة السرية للمستخدم",
|
||||
"log_app_addaccess": "إضافة ترخيص بالنفاذ إلى '{}'",
|
||||
"password_too_simple_1": "يجب أن يكون طول الكلمة السرية على الأقل 8 حروف"
|
||||
"password_too_simple_1": "يجب أن يكون طول الكلمة السرية على الأقل 8 حروف",
|
||||
"service_description_php7.0-fpm": "يُشغّل التطبيقات المكتوبة بلغة الـ PHP على Nginx",
|
||||
"updating_app_lists": "جارٍ جلب التحديثات المتوفرة الخاصة بالتطبيقات…",
|
||||
"already_up_to_date": "كل شيء على ما يرام! ليس هناك ما يتطلّب تحديثًا!",
|
||||
"service_description_nslcd": "يدير اتصال متسخدمي واي يونوهوست عبر طرفية سطر الأوامر",
|
||||
"service_description_slapd": "يخزّن المستخدمين والنطاقات والمعلومات المتعلقة بها",
|
||||
"service_reloaded": "تم إعادة تحميل خدمة '{service:s}'",
|
||||
"service_restarted": "تم إعادة تشغيل خدمة '{service:s}'",
|
||||
"group_unknown": "الفريق {group:s} مجهول",
|
||||
"group_deletion_failed": "فشلت عملية حذف الفريق '{group}'",
|
||||
"group_deleted": "تم حذف الفريق '{group}'",
|
||||
"group_created": "تم إنشاء الفريق '{group}' بنجاح",
|
||||
"group_name_already_exist": "الفريق {name:s} موجود بالفعل",
|
||||
"error_when_removing_sftpuser_group": "حدث خطأ أثناء محاولة حذف فريق sftpusers",
|
||||
"dyndns_could_not_check_available": "لا يمكن التحقق مِن أنّ {domain:s} متوفر على {provider:s}.",
|
||||
"backup_mount_archive_for_restore": "جارٍ تهيئة النسخة الاحتياطية للاسترجاع…",
|
||||
"root_password_replaced_by_admin_password": "لقد تم استبدال كلمة سر الجذر root بالكلمة الإدارية لـ admin.",
|
||||
"app_upgrade_stopped": "لقد تم إلغاء تحديث كافة التطبيقات لتجنب حادث بسبب فشل تحديث التطبيق السابق"
|
||||
}
|
||||
|
|
478
locales/ca.json
478
locales/ca.json
|
@ -1,31 +1,31 @@
|
|||
{
|
||||
"action_invalid": "Acció '{action:s}' invàlida",
|
||||
"admin_password": "Contrasenya d'administració",
|
||||
"admin_password_change_failed": "No s'ha pogut canviar la contrasenya",
|
||||
"admin_password_change_failed": "No es pot canviar la contrasenya",
|
||||
"admin_password_changed": "S'ha canviat la contrasenya d'administració",
|
||||
"app_already_installed": "{app:s} ja està instal·lada",
|
||||
"app_already_installed_cant_change_url": "Aquesta aplicació ja està instal·lada. La URL no és pot canviar únicament amb aquesta funció. Mireu a \"app changeurl\" si està disponible.",
|
||||
"app_already_up_to_date": "{app:s} ja està actualitzada",
|
||||
"app_argument_choice_invalid": "Aquesta opció no és vàlida per l'argument '{name:s}', ha de ser una de {choices:s}",
|
||||
"app_argument_invalid": "Valor invàlid per l'argument '{name:s}':{error:s}",
|
||||
"app_argument_choice_invalid": "Utilitzeu una de les opcions «{choices:s}» per l'argument «{name:s}»",
|
||||
"app_argument_invalid": "Escolliu un valor vàlid per l'argument «{name:s}»: {error:s}",
|
||||
"app_argument_required": "Es necessita l'argument '{name:s}'",
|
||||
"app_change_no_change_url_script": "L'aplicació {app_name:s} encara no permet canviar la seva URL, es possible que s'hagi d'actualitzar.",
|
||||
"app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar nginx. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors:s}",
|
||||
"app_change_url_failed_nginx_reload": "No s'ha pogut tornar a carregar NGINX. Aquí teniu el resultat de \"nginx -t\":\n{nginx_errors:s}",
|
||||
"app_change_url_identical_domains": "L'antic i el nou domini/camí són idèntics ('{domain:s}{path:s}'), no hi ha res per fer.",
|
||||
"app_change_url_no_script": "Aquesta aplicació '{app_name:s}' encara no permet modificar la URL. Potser s'ha d'actualitzar l'aplicació.",
|
||||
"app_change_url_success": "La URL de {app:s} s'ha canviat correctament a {domain:s}{path:s}",
|
||||
"app_change_url_no_script": "L'aplicació '{app_name:s}' encara no permet modificar la URL. Potser s'ha d'actualitzar.",
|
||||
"app_change_url_success": "La URL de {app:s} ara és {domain:s}{path:s}",
|
||||
"app_extraction_failed": "No s'han pogut extreure els fitxers d'instal·lació",
|
||||
"app_id_invalid": "Id de l'aplicació incorrecte",
|
||||
"app_id_invalid": "ID de l'aplicació incorrecte",
|
||||
"app_incompatible": "L'aplicació {app} no és compatible amb la teva versió de YunoHost",
|
||||
"app_install_files_invalid": "Fitxers d'instal·lació invàlids",
|
||||
"app_location_already_used": "L'aplicació '{app}' ja està instal·lada en aquest camí ({path})",
|
||||
"app_make_default_location_already_used": "No es pot fer l'aplicació '{app}' per defecte en el domini {domain} ja que ja és utilitzat per una altra aplicació '{other_app}'",
|
||||
"app_location_install_failed": "No s'ha pogut instal·lar l'aplicació en aquest camí ja que entra en conflicte amb l'aplicació '{other_app}' ja instal·lada a '{other_path}'",
|
||||
"app_location_unavailable": "Aquesta url no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps:s}",
|
||||
"app_manifest_invalid": "Manifest d'aplicació incorrecte: {error}",
|
||||
"app_install_files_invalid": "Aquests fitxers no es poden instal·lar",
|
||||
"app_location_already_used": "L'aplicació «{app}» ja està instal·lada en ({path})",
|
||||
"app_make_default_location_already_used": "No es pot fer l'aplicació '{app}' per defecte en el domini «{domain}» ja que ja és utilitzat per una altra aplicació '{other_app}'",
|
||||
"app_location_install_failed": "No s'ha pogut instal·lar l'aplicació aquí ja que entra en conflicte amb l'aplicació «{other_app}» ja instal·lada a «{other_path}»",
|
||||
"app_location_unavailable": "Aquesta URL no està disponible o entra en conflicte amb aplicacions ja instal·lades:\n{apps:s}",
|
||||
"app_manifest_invalid": "Hi ha algun error amb el manifest de l'aplicació: {error}",
|
||||
"app_no_upgrade": "No hi ha cap aplicació per actualitzar",
|
||||
"app_not_correctly_installed": "{app:s} sembla estar mal instal·lada",
|
||||
"app_not_installed": "{app:s} no està instal·lada",
|
||||
"app_not_installed": "No s'ha trobat l'aplicació «{app:s}» en la llista d'aplicacions instal·lades: {all_apps}",
|
||||
"app_not_properly_removed": "{app:s} no s'ha pogut suprimir correctament",
|
||||
"app_package_need_update": "El paquet de l'aplicació {app} ha de ser actualitzat per poder seguir els canvis de YunoHost",
|
||||
"app_removed": "{app:s} ha estat suprimida",
|
||||
|
@ -35,22 +35,22 @@
|
|||
"app_sources_fetch_failed": "No s'han pogut carregar els fitxers font, l'URL és correcta?",
|
||||
"app_unknown": "Aplicació desconeguda",
|
||||
"app_unsupported_remote_type": "El tipus remot utilitzat per l'aplicació no està suportat",
|
||||
"app_upgrade_app_name": "Actualitzant l'aplicació {app}…",
|
||||
"app_upgrade_app_name": "Actualitzant {app}…",
|
||||
"app_upgrade_failed": "No s'ha pogut actualitzar {app:s}",
|
||||
"app_upgrade_some_app_failed": "No s'han pogut actualitzar algunes aplicacions",
|
||||
"app_upgraded": "{app:s} ha estat actualitzada",
|
||||
"app_upgraded": "S'ha actualitzat {app:s}",
|
||||
"appslist_corrupted_json": "No s'han pogut carregar les llistes d'aplicacions. Sembla que {filename:s} està danyat.",
|
||||
"appslist_could_not_migrate": "No s'ha pogut migrar la llista d'aplicacions {appslist:s}! No s'ha pogut analitzar la URL... L'antic cronjob s'ha guardat a {bkp_file:s}.",
|
||||
"appslist_fetched": "S'ha descarregat la llista d'aplicacions {appslist:s} correctament",
|
||||
"appslist_migrating": "Migrant la llista d'aplicacions {appslist:s}…",
|
||||
"appslist_could_not_migrate": "No s'ha pogut migrar la llista d'aplicacions «{appslist:s}»! No s'ha pogut analitzar la URL... L'antic cronjob s'ha guardat a {bkp_file:s}.",
|
||||
"appslist_fetched": "S'ha actualitzat la llista d'aplicacions «{appslist:s}»",
|
||||
"appslist_migrating": "Migrant la llista d'aplicacions «{appslist:s}»…",
|
||||
"appslist_name_already_tracked": "Ja hi ha una llista d'aplicacions registrada amb el nom {name:s}.",
|
||||
"appslist_removed": "S'ha eliminat la llista d'aplicacions {appslist:s}",
|
||||
"appslist_retrieve_bad_format": "L'arxiu obtingut per la llista d'aplicacions {appslist:s} no és vàlid",
|
||||
"appslist_retrieve_error": "No s'ha pogut obtenir la llista d'aplicacions remota {appslist:s}: {error:s}",
|
||||
"appslist_unknown": "La llista d'aplicacions {appslist:s} es desconeguda.",
|
||||
"appslist_removed": "S'ha eliminat la llista d'aplicacions «{appslist:s}»",
|
||||
"appslist_retrieve_bad_format": "No s'ha pogut llegir la llista d'aplicacions obtinguda «{appslist:s}»",
|
||||
"appslist_retrieve_error": "No s'ha pogut obtenir la llista d'aplicacions remota «{appslist:s}»: {error:s}",
|
||||
"appslist_unknown": "La llista d'aplicacions «{appslist:s}» es desconeguda.",
|
||||
"appslist_url_already_tracked": "Ja hi ha una llista d'aplicacions registrada amb al URL {url:s}.",
|
||||
"ask_current_admin_password": "Contrasenya d'administrador actual",
|
||||
"ask_email": "Correu electrònic",
|
||||
"ask_email": "Adreça de correu electrònic",
|
||||
"ask_firstname": "Nom",
|
||||
"ask_lastname": "Cognom",
|
||||
"ask_list_to_remove": "Llista per a suprimir",
|
||||
|
@ -58,56 +58,56 @@
|
|||
"ask_new_admin_password": "Nova contrasenya d'administrador",
|
||||
"ask_password": "Contrasenya",
|
||||
"ask_path": "Camí",
|
||||
"backup_abstract_method": "Encara no s'ha implementat aquest mètode de copia de seguretat",
|
||||
"backup_abstract_method": "Encara està per implementar aquest mètode de còpia de seguretat",
|
||||
"backup_action_required": "S'ha d'especificar què s'ha de guardar",
|
||||
"backup_app_failed": "No s'ha pogut fer la còpia de seguretat de l'aplicació \"{app:s}\"",
|
||||
"backup_applying_method_borg": "Enviant tots els fitxers de la còpia de seguretat al repositori borg-backup…",
|
||||
"backup_applying_method_copy": "Còpia de tots els fitxers a la còpia de seguretat…",
|
||||
"backup_applying_method_custom": "Crida del mètode de còpia de seguretat personalitzat \"{method:s}\"…",
|
||||
"backup_applying_method_tar": "Creació de l'arxiu tar de la còpia de seguretat…",
|
||||
"backup_archive_app_not_found": "L'aplicació \"{app:s}\" no es troba dins l'arxiu de la còpia de seguretat",
|
||||
"backup_applying_method_tar": "Creació de l'arxiu TAR de la còpia de seguretat…",
|
||||
"backup_archive_app_not_found": "No s'ha pogut trobar l'aplicació «{app:s}» dins l'arxiu de la còpia de seguretat",
|
||||
"backup_archive_broken_link": "No s'ha pogut accedir a l'arxiu de la còpia de seguretat (enllaç invàlid cap a {path:s})",
|
||||
"backup_archive_mount_failed": "No s'ha pogut carregar l'arxiu de la còpia de seguretat",
|
||||
"backup_archive_name_exists": "Ja hi ha una còpia de seguretat amb aquest nom",
|
||||
"backup_archive_name_exists": "Ja hi ha una còpia de seguretat amb aquest nom.",
|
||||
"backup_archive_name_unknown": "Còpia de seguretat local \"{name:s}\" desconeguda",
|
||||
"backup_archive_open_failed": "No s'ha pogut obrir l'arxiu de la còpia de seguretat",
|
||||
"backup_archive_system_part_not_available": "La part \"{part:s}\" del sistema no està disponible en aquesta copia de seguretat",
|
||||
"backup_archive_writing_error": "No es poden afegir arxius a l'arxiu comprimit de la còpia de seguretat",
|
||||
"backup_ask_for_copying_if_needed": "Alguns fitxers no s'han pogut preparar per la còpia de seguretat utilitzant el mètode que evita malgastar espai del sistema temporalment. Per fer la còpia de seguretat, s'han d'utilitzar {size:s}MB temporalment. Hi esteu d'acord?",
|
||||
"backup_archive_system_part_not_available": "La part «{part:s}» del sistema no està disponible en aquesta copia de seguretat",
|
||||
"backup_archive_writing_error": "No es poden afegir els arxius «{source:s}» (anomenats en l'arxiu «{dest:s}») a l'arxiu comprimit de la còpia de seguretat «{archive:s}»",
|
||||
"backup_ask_for_copying_if_needed": "Voleu fer la còpia de seguretat utilitzant {size:s} MB temporalment? (S'utilitza aquest mètode ja que alguns dels fitxers no s'han pogut preparar utilitzar un mètode més eficient.)",
|
||||
"backup_borg_not_implemented": "El mètode de còpia de seguretat Borg encara no està implementat",
|
||||
"backup_cant_mount_uncompress_archive": "No es pot carregar en mode de lectura només el directori de l'arxiu descomprimit",
|
||||
"backup_cant_mount_uncompress_archive": "No es pot carregar l'arxiu descomprimit com a protegit contra escriptura",
|
||||
"backup_cleaning_failed": "No s'ha pogut netejar el directori temporal de la còpia de seguretat",
|
||||
"backup_copying_to_organize_the_archive": "Copiant {size:s}MB per organitzar l'arxiu",
|
||||
"backup_couldnt_bind": "No es pot lligar {src:s} amb {dest:s}.",
|
||||
"backup_created": "S'ha creat la còpia de seguretat",
|
||||
"backup_creating_archive": "Creant l'arxiu de la còpia de seguretat…",
|
||||
"aborting": "Avortant.",
|
||||
"app_not_upgraded": "Les següents aplicacions no s'han actualitzat: {apps}",
|
||||
"app_start_install": "instal·lant l'aplicació {app}…",
|
||||
"app_start_remove": "Eliminant l'aplicació {app}…",
|
||||
"app_start_backup": "Recuperant els fitxers pels que s'ha de fer una còpia de seguretat per {app}…",
|
||||
"app_start_restore": "Recuperant l'aplicació {app}…",
|
||||
"app_not_upgraded": "L'aplicació «{failed_app}» no s'ha pogut actualitzar, i com a conseqüència l'actualització de les següents aplicacions ha estat cancel·lada: {apps}",
|
||||
"app_start_install": "instal·lant l'aplicació «{app}»…",
|
||||
"app_start_remove": "Eliminant l'aplicació «{app}»…",
|
||||
"app_start_backup": "Recuperant els fitxers pels que s'ha de fer una còpia de seguretat per «{app}»…",
|
||||
"app_start_restore": "Recuperant l'aplicació «{app}»…",
|
||||
"app_upgrade_several_apps": "S'actualitzaran les següents aplicacions: {apps}",
|
||||
"ask_new_domain": "Nou domini",
|
||||
"ask_new_path": "Nou camí",
|
||||
"backup_actually_backuping": "S'està creant un arxiu de còpia de seguretat a partir dels fitxers recuperats…",
|
||||
"backup_creation_failed": "Ha fallat la creació de la còpia de seguretat",
|
||||
"backup_actually_backuping": "Creant un arxiu de còpia de seguretat a partir dels fitxers recuperats…",
|
||||
"backup_creation_failed": "No s'ha pogut crear l'arxiu de la còpia de seguretat",
|
||||
"backup_csv_addition_failed": "No s'han pogut afegir fitxers per a fer-ne la còpia de seguretat al fitxer CSV",
|
||||
"backup_csv_creation_failed": "No s'ha pogut crear el fitxer CSV necessari per a futures operacions de recuperació",
|
||||
"backup_custom_backup_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa \"backup\"",
|
||||
"backup_custom_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa \"mount\"",
|
||||
"backup_csv_creation_failed": "No s'ha pogut crear el fitxer CSV necessari per a la restauració",
|
||||
"backup_custom_backup_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa «backup»",
|
||||
"backup_custom_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa «mount»",
|
||||
"backup_custom_need_mount_error": "El mètode de còpia de seguretat personalitzat ha fallat a l'etapa \"need_mount\"",
|
||||
"backup_delete_error": "No s'ha pogut suprimir \"{path:s}\"",
|
||||
"backup_delete_error": "No s'ha pogut suprimir «{path:s}»",
|
||||
"backup_deleted": "S'ha suprimit la còpia de seguretat",
|
||||
"backup_extracting_archive": "Extraient l'arxiu de la còpia de seguretat…",
|
||||
"backup_hook_unknown": "Script de còpia de seguretat \"{hook:s}\" desconegut",
|
||||
"backup_invalid_archive": "Arxiu de còpia de seguretat no vàlid",
|
||||
"backup_method_borg_finished": "La còpia de seguretat a borg ha acabat",
|
||||
"backup_hook_unknown": "Script de còpia de seguretat «{hook:s}» desconegut",
|
||||
"backup_invalid_archive": "Aquest no és un arxiu de còpia de seguretat",
|
||||
"backup_method_borg_finished": "La còpia de seguretat a Borg ha acabat",
|
||||
"backup_method_copy_finished": "La còpia de la còpia de seguretat ha acabat",
|
||||
"backup_method_custom_finished": "El mètode de còpia de seguretat personalitzat \"{method:s}\" ha acabat",
|
||||
"backup_method_tar_finished": "S'ha creat l'arxiu de còpia de seguretat tar",
|
||||
"backup_method_tar_finished": "S'ha creat l'arxiu de còpia de seguretat TAR",
|
||||
"backup_mount_archive_for_restore": "Preparant l'arxiu per la restauració…",
|
||||
"good_practices_about_user_password": "Esteu a punt de definir una nova contrasenya d'usuari. 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_user_password": "Esteu a punt de definir una nova contrasenya d'usuari. 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).",
|
||||
"password_listed": "Aquesta contrasenya és una de les més utilitzades en el món. Si us plau utilitzeu-ne una més única.",
|
||||
"password_too_simple_1": "La contrasenya ha de tenir un mínim de 8 caràcters",
|
||||
"password_too_simple_2": "La contrasenya ha de tenir un mínim de 8 caràcters i ha de contenir dígits, majúscules i minúscules",
|
||||
|
@ -115,42 +115,42 @@
|
|||
"password_too_simple_4": "La contrasenya ha de tenir un mínim de 12 caràcters i tenir dígits, majúscules, minúscules i caràcters especials",
|
||||
"backup_no_uncompress_archive_dir": "El directori de l'arxiu descomprimit no existeix",
|
||||
"backup_nothings_done": "No hi ha res a guardar",
|
||||
"backup_output_directory_forbidden": "Directori de sortida no permès. Les còpies de seguretat no es poden crear ni dins els directoris /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ni dins els subdirectoris /home/yunohost.backup/archives",
|
||||
"backup_output_directory_not_empty": "El directori de sortida no està buit",
|
||||
"backup_output_directory_forbidden": "Escolliu un directori de sortida different. Les còpies de seguretat no es poden crear ni dins els directoris /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ni dins els subdirectoris /home/yunohost.backup/archives",
|
||||
"backup_output_directory_not_empty": "Heu d'escollir un directori de sortida buit",
|
||||
"backup_output_directory_required": "Heu d'especificar un directori de sortida per la còpia de seguretat",
|
||||
"backup_output_symlink_dir_broken": "Teniu un enllaç simbòlic trencat en lloc del directori dels arxius '{path:s}'. Pot ser teniu una configuració per la còpia de seguretat específica en un altre sistema de fitxers, si és el cas segurament heu oblidat muntar o connectar el disc dur o la clau USB.",
|
||||
"backup_php5_to_php7_migration_may_fail": "No s'ha pogut convertir l'arxiu per suportar php7, la restauració de les vostres aplicacions pot fallar (raó: {error:s})",
|
||||
"backup_output_symlink_dir_broken": "El directori del arxiu «{path:s}» es un enllaç simbòlic trencat. Pot ser heu oblidat muntar, tornar a muntar o connectar el mitja d'emmagatzematge al que apunta.",
|
||||
"backup_php5_to_php7_migration_may_fail": "No s'ha pogut convertir l'arxiu per suportar PHP 7, pot ser que no es puguin restaurar les vostres aplicacions PHP (raó: {error:s})",
|
||||
"backup_running_hooks": "Executant els scripts de la còpia de seguretat…",
|
||||
"backup_system_part_failed": "No s'ha pogut fer la còpia de seguretat de la part \"{part:s}\" del sistema",
|
||||
"backup_unable_to_organize_files": "No s'han pogut organitzar els fitxers dins de l'arxiu amb el mètode ràpid",
|
||||
"backup_with_no_backup_script_for_app": "L'aplicació {app:s} no té un script de còpia de seguretat. Serà ignorat.",
|
||||
"backup_with_no_restore_script_for_app": "L'aplicació {app:s} no té un script de restauració, no podreu restaurar automàticament la còpia de seguretat d'aquesta aplicació.",
|
||||
"certmanager_acme_not_configured_for_domain": "El certificat pel domini {domain:s} sembla que no està instal·lat correctament. Si us plau executeu primer cert-install per aquest domini.",
|
||||
"certmanager_attempt_to_renew_nonLE_cert": "El certificat pel domini {domain:s} no ha estat emès per Let's Encrypt. No es pot renovar automàticament!",
|
||||
"certmanager_attempt_to_renew_valid_cert": "El certificat pel domini {domain:s} està a punt de caducar! (Utilitzeu --force si sabeu el que esteu fent)",
|
||||
"backup_unable_to_organize_files": "No s'ha pogut utilitzar el mètode ràpid per organitzar els fitxers dins de l'arxiu",
|
||||
"backup_with_no_backup_script_for_app": "L'aplicació «{app:s}» no té un script de còpia de seguretat. Serà ignorat.",
|
||||
"backup_with_no_restore_script_for_app": "L'aplicació «{app:s}» no té un script de restauració, no podreu restaurar automàticament la còpia de seguretat d'aquesta aplicació.",
|
||||
"certmanager_acme_not_configured_for_domain": "El certificat pel domini «{domain:s}» sembla que no està instal·lat correctament. Si us plau executeu primer «cert-install» per aquest domini.",
|
||||
"certmanager_attempt_to_renew_nonLE_cert": "El certificat pel domini «{domain:s}» no ha estat emès per Let's Encrypt. No es pot renovar automàticament!",
|
||||
"certmanager_attempt_to_renew_valid_cert": "El certificat pel domini «{domain:s}» està a punt de caducar! (Utilitzeu --force si sabeu el que esteu fent)",
|
||||
"certmanager_attempt_to_replace_valid_cert": "Esteu intentant sobreescriure un certificat correcte i vàlid pel domini {domain:s}! (Utilitzeu --force per ometre)",
|
||||
"certmanager_cannot_read_cert": "S'ha produït un error al intentar obrir el certificat actual pel domini {domain:s} (arxiu: {file:s}), raó: {reason:s}",
|
||||
"certmanager_cert_install_success": "S'ha instal·lat correctament un certificat Let's Encrypt pel domini {domain:s}!",
|
||||
"certmanager_cert_install_success_selfsigned": "S'ha instal·lat correctament un certificat auto-signat pel domini {domain:s}!",
|
||||
"certmanager_cert_renew_success": "S'ha renovat correctament el certificat Let's Encrypt pel domini {domain:s}!",
|
||||
"certmanager_cert_install_success": "S'ha instal·lat correctament un certificat Let's Encrypt pel domini «{domain:s}»",
|
||||
"certmanager_cert_install_success_selfsigned": "S'ha instal·lat correctament un certificat auto-signat pel domini «{domain:s}»",
|
||||
"certmanager_cert_renew_success": "S'ha renovat correctament el certificat Let's Encrypt pel domini «{domain:s}»",
|
||||
"certmanager_cert_signing_failed": "No s'ha pogut firmar el nou certificat",
|
||||
"certmanager_certificate_fetching_or_enabling_failed": "Sembla que l'activació del nou certificat per {domain:s} ha fallat…",
|
||||
"certmanager_conflicting_nginx_file": "No s'ha pogut preparar el domini per al desafiament ACME: l'arxiu de configuració nginx {filepath:s} entra en conflicte i s'ha d'eliminar primer",
|
||||
"certmanager_certificate_fetching_or_enabling_failed": "Sembla que utilitzar el nou certificat per {domain:s} ha fallat…",
|
||||
"certmanager_conflicting_nginx_file": "No s'ha pogut preparar el domini per al desafiament ACME: l'arxiu de configuració NGINX {filepath:s} entra en conflicte i s'ha d'eliminar primer",
|
||||
"certmanager_couldnt_fetch_intermediate_cert": "S'ha exhaurit el temps d'esperar al intentar recollir el certificat intermedi des de Let's Encrypt. La instal·lació/renovació del certificat s'ha cancel·lat - torneu a intentar-ho més tard.",
|
||||
"certmanager_domain_cert_not_selfsigned": "El certificat pel domini {domain:s} no és auto-signat Esteu segur de voler canviar-lo? (Utilitzeu --force per fer-ho)",
|
||||
"certmanager_domain_dns_ip_differs_from_public_ip": "El registre DNS \"A\" pel domini {domain:s} és diferent a l'adreça IP d'aquest servidor. Si heu modificat recentment el registre A, si us plau espereu a que es propagui (hi ha eines per verificar la propagació disponibles a internet). (Si sabeu el que esteu fent, podeu utilitzar --no-checks per desactivar aquestes comprovacions.)",
|
||||
"certmanager_domain_http_not_working": "Sembla que el domini {domain:s} no és accessible via HTTP. Si us plau verifiqueu que les configuracions DNS i nginx siguin correctes",
|
||||
"certmanager_domain_cert_not_selfsigned": "El certificat pel domini {domain:s} no és auto-signat Esteu segur de voler canviar-lo? (Utilitzeu «--force» per fer-ho)",
|
||||
"certmanager_domain_dns_ip_differs_from_public_ip": "El registre DNS \"A\" pel domini «{domain:s}» és diferent a l'adreça IP d'aquest servidor. Si heu modificat recentment el registre A, si us plau espereu a que es propagui (hi ha eines per verificar la propagació disponibles a internet). (Si sabeu el que esteu fent, podeu utilitzar «--no-checks» per desactivar aquestes comprovacions.)",
|
||||
"certmanager_domain_http_not_working": "Sembla que el domini {domain:s} no és accessible via HTTP. Verifiqueu que les configuracions DNS i NGINX siguin correctes",
|
||||
"certmanager_domain_not_resolved_locally": "El domini {domain:s} no es pot resoldre dins del vostre servidor YunoHost. Això pot passar si heu modificat recentment el registre DNS. Si és així, si us plau espereu unes hores per a que es propagui. Si el problema continua, considereu afegir {domain:s} a /etc/hosts. (Si sabeu el que esteu fent, podeu utilitzar --no-checks per desactivar aquestes comprovacions.)",
|
||||
"certmanager_domain_unknown": "Domini desconegut {domain:s}",
|
||||
"certmanager_error_no_A_record": "No s'ha trobat cap registre DNS \"A\" per {domain:s}. Heu de fer que el vostre nom de domini apunti cap a la vostra màquina per tal de poder instal·lar un certificat Let's Encrypt! (Si sabeu el que esteu fent, podeu utilitzar --no-checks per desactivar aquestes comprovacions.)",
|
||||
"certmanager_domain_unknown": "Domini desconegut «{domain:s}»",
|
||||
"certmanager_error_no_A_record": "No s'ha trobat cap registre DNS «A» per «{domain:s}». Heu de fer que el vostre nom de domini apunti cap a la vostra màquina per tal de poder instal·lar un certificat Let's Encrypt. (Si sabeu el que esteu fent, podeu utilitzar «--no-checks» per desactivar aquestes comprovacions.)",
|
||||
"certmanager_hit_rate_limit": "S'han emès massa certificats recentment per aquest mateix conjunt de dominis {domain:s}. Si us plau torneu-ho a intentar més tard. Consulteu https://letsencrypt.org/docs/rate-limits/ per obtenir més detalls",
|
||||
"certmanager_http_check_timeout": "S'ha exhaurit el temps d'espera quan el servidor ha intentat contactar amb ell mateix via HTTP utilitzant la seva adreça IP pública (domini domain:s} amb IP {ip:s}). Pot ser degut a hairpinning o a que el talla focs/router al que està connectat el servidor estan mal configurats.",
|
||||
"certmanager_http_check_timeout": "S'ha exhaurit el temps d'espera quan el servidor ha intentat contactar amb ell mateix via HTTP utilitzant la seva adreça IP pública (domini «{domain:s}» amb IP «{ip:s}»). Pot ser degut a hairpinning o a que el talla focs/router al que està connectat el servidor estan mal configurats.",
|
||||
"certmanager_no_cert_file": "No s'ha pogut llegir l'arxiu del certificat pel domini {domain:s} (fitxer: {file:s})",
|
||||
"certmanager_self_ca_conf_file_not_found": "No s'ha trobat el fitxer de configuració per l'autoritat del certificat auto-signat (fitxer: {file:s})",
|
||||
"certmanager_unable_to_parse_self_CA_name": "No s'ha pogut analitzar el nom de l'autoritat del certificat auto-signat (fitxer: {file:s})",
|
||||
"confirm_app_install_warning": "Atenció: aquesta aplicació funciona però no està ben integrada amb YunoHost. Algunes característiques com la autenticació única i la còpia de seguretat/restauració poden no estar disponibles. Voleu instal·lar-la de totes maneres? [{answers:s}] ",
|
||||
"confirm_app_install_danger": "ATENCIÓ! Aquesta aplicació encara és experimental (si no és que no funciona directament) i és probable que trenqui el sistema! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. Esteu segurs de voler córrer aquest risc? [{answers:s}] ",
|
||||
"confirm_app_install_thirdparty": "ATENCIÓ! La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. Faci-ho sota la seva responsabilitat.No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. Esteu segurs de voler córrer aquest risc? [{answers:s}] ",
|
||||
"confirm_app_install_warning": "Atenció: Aquesta aplicació funciona, però no està ben integrada amb YunoHost. Algunes característiques com la autenticació única i la còpia de seguretat/restauració poden no estar disponibles. Voleu instal·lar-la de totes maneres? [{answers:s}] ",
|
||||
"confirm_app_install_danger": "PERILL! Aquesta aplicació encara és experimental (si no és que no funciona directament)! No hauríeu d'instal·lar-la a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers:s}»",
|
||||
"confirm_app_install_thirdparty": "PERILL! Aquesta aplicació no es part del catàleg d'aplicacions de YunoHost. La instal·lació d'aplicacions de terceres parts pot comprometre la integritat i seguretat del seu sistema. No hauríeu d'instal·lar-ne a no ser que sapigueu el que feu. No obtindreu CAP AJUDA si l'aplicació no funciona o trenca el sistema… Si accepteu el risc, escriviu «{answers:s}»",
|
||||
"custom_app_url_required": "Heu de especificar una URL per actualitzar la vostra aplicació personalitzada {app:s}",
|
||||
"custom_appslist_name_required": "Heu d'especificar un nom per la vostra llista d'aplicacions personalitzada",
|
||||
"diagnosis_debian_version_error": "No s'ha pogut obtenir la versió Debian: {error}",
|
||||
|
@ -158,24 +158,24 @@
|
|||
"diagnosis_monitor_disk_error": "No es poden monitorar els discs: {error}",
|
||||
"diagnosis_monitor_network_error": "No es pot monitorar la xarxa: {error}",
|
||||
"diagnosis_monitor_system_error": "No es pot monitorar el sistema: {error}",
|
||||
"diagnosis_no_apps": "No hi ha cap aplicació instal·lada",
|
||||
"diagnosis_no_apps": "Aquesta aplicació no està instal·lada",
|
||||
"admin_password_too_long": "Trieu una contrasenya de menys de 127 caràcters",
|
||||
"dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/apt (els gestors de paquets del sistema) sembla estar mal configurat... Podeu intentar solucionar-ho connectant-vos per ssh i executant \"sudo dpkg --configure -a\".",
|
||||
"dpkg_is_broken": "No es pot fer això en aquest instant perquè dpkg/APT (els gestors de paquets del sistema) sembla estar mal configurat… Podeu intentar solucionar-ho connectant-vos per SSH i executant «sudo dpkg --configure -a».",
|
||||
"dnsmasq_isnt_installed": "sembla que dnsmasq no està instal·lat, executeu \"apt-get remove bind9 && apt-get install dnsmasq\"",
|
||||
"domain_cannot_remove_main": "No es pot eliminar el domini principal. S'ha d'establir un nou domini primer",
|
||||
"domain_cert_gen_failed": "No s'ha pogut generar el certificat",
|
||||
"domain_created": "S'ha creat el domini",
|
||||
"domain_creation_failed": "No s'ha pogut crear el domini",
|
||||
"domain_creation_failed": "No s'ha pogut crear el domini {domain}: {error}",
|
||||
"domain_deleted": "S'ha eliminat el domini",
|
||||
"domain_deletion_failed": "No s'ha pogut eliminar el domini",
|
||||
"domain_deletion_failed": "No s'ha pogut eliminar el domini {domini}: {error}",
|
||||
"domain_exists": "El domini ja existeix",
|
||||
"app_action_cannot_be_ran_because_required_services_down": "Aquesta aplicació necessita serveis que estan aturats. Abans de continuar, hauríeu d'intentar arrancar de nou els serveis següents (i també investigar perquè estan aturats) : {services}",
|
||||
"app_action_cannot_be_ran_because_required_services_down": "Aquests serveis necessaris haurien d'estar funcionant per poder executar aquesta acció: {services} Intenteu reiniciar-los per continuar (i possiblement investigar perquè estan aturats).",
|
||||
"domain_dns_conf_is_just_a_recommendation": "Aquesta ordre mostra la configuració *recomanada*. En cap cas fa la configuració del DNS. És la vostra responsabilitat configurar la zona DNS en el vostre registrar en acord amb aquesta recomanació.",
|
||||
"domain_dyndns_already_subscribed": "Ja us heu subscrit a un domini DynDNS",
|
||||
"domain_dyndns_dynette_is_unreachable": "No s'ha pogut abastar la dynette YunoHost, o bé YunoHost no està connectat a internet correctament o bé el servidor dynette està caigut. Error: {error}",
|
||||
"domain_dyndns_invalid": "Domini no vàlid per utilitzar amb DynDNS",
|
||||
"domain_dyndns_root_unknown": "Domini DynDNS principal desconegut",
|
||||
"domain_hostname_failed": "No s'ha pogut establir un nou nom d'amfitrió",
|
||||
"domain_hostname_failed": "No s'ha pogut establir un nou nom d'amfitrió. Això podria causar problemes més tard (podria no passar res).",
|
||||
"domain_uninstall_app_first": "Hi ha una o més aplicacions instal·lades en aquest domini. Desinstal·leu les abans d'eliminar el domini",
|
||||
"domain_unknown": "Domini desconegut",
|
||||
"domain_zone_exists": "El fitxer de zona DNS ja existeix",
|
||||
|
@ -187,61 +187,61 @@
|
|||
"dyndns_could_not_check_available": "No s'ha pogut verificar la disponibilitat de {domain:s} a {provider:s}.",
|
||||
"dyndns_ip_update_failed": "No s'ha pogut actualitzar l'adreça IP al DynDNS",
|
||||
"dyndns_ip_updated": "S'ha actualitzat l'adreça IP al DynDNS",
|
||||
"dyndns_key_generating": "S'està generant la clau DNS, això pot trigar una estona…",
|
||||
"dyndns_key_generating": "S'està generant la clau DNS… això pot trigar una estona.",
|
||||
"dyndns_key_not_found": "No s'ha trobat la clau DNS pel domini",
|
||||
"dyndns_no_domain_registered": "No hi ha cap domini registrat amb DynDNS",
|
||||
"dyndns_registered": "S'ha registrat el domini DynDNS",
|
||||
"dyndns_registration_failed": "No s'ha pogut registrar el domini DynDNS: {error:s}",
|
||||
"dyndns_domain_not_provided": "El proveïdor {provider:s} no pot oferir el domini {domain:s}.",
|
||||
"dyndns_domain_not_provided": "El proveïdor de DynDNS {provider:s} no pot oferir el domini {domain:s}.",
|
||||
"dyndns_unavailable": "El domini {domain:s} no està disponible.",
|
||||
"executing_command": "Execució de l'ordre « {command:s} »…",
|
||||
"executing_script": "Execució de l'script « {script:s} »…",
|
||||
"extracting": "Extracció en curs…",
|
||||
"dyndns_cron_installed": "S'ha instal·lat la tasca cron pel DynDNS",
|
||||
"dyndns_cron_remove_failed": "No s'ha pogut eliminar la tasca cron pel DynDNS",
|
||||
"dyndns_cron_installed": "S'ha creat la tasca cron pel DynDNS",
|
||||
"dyndns_cron_remove_failed": "No s'ha pogut eliminar la tasca cron per a DynDNS: {error}",
|
||||
"dyndns_cron_removed": "S'ha eliminat la tasca cron pel DynDNS",
|
||||
"experimental_feature": "Atenció: aquesta funcionalitat és experimental i no es considera estable, no s'ha d'utilitzar a excepció de saber el que esteu fent.",
|
||||
"experimental_feature": "Atenció: Aquesta funcionalitat és experimental i no es considera estable, no s'ha d'utilitzar a excepció de saber el que esteu fent.",
|
||||
"field_invalid": "Camp incorrecte « {:s} »",
|
||||
"file_does_not_exist": "El camí {path:s} no existeix.",
|
||||
"firewall_reload_failed": "No s'ha pogut tornar a carregar el tallafoc",
|
||||
"firewall_reloaded": "S'ha tornat a carregar el tallafoc",
|
||||
"firewall_rules_cmd_failed": "No s'han pogut aplicar algunes regles del tallafoc. Mireu el registre per a més informació.",
|
||||
"firewall_reload_failed": "No s'ha pogut tornar a carregar el tallafocs",
|
||||
"firewall_reloaded": "S'ha tornat a carregar el tallafocs",
|
||||
"firewall_rules_cmd_failed": "No s'han pogut aplicar algunes regles del tallafocs. Més informació en el registre.",
|
||||
"format_datetime_short": "%d/%m/%Y %H:%M",
|
||||
"global_settings_bad_choice_for_enum": "Opció pel paràmetre {setting:s} incorrecta, s'ha rebut «{choice:s}» però les opcions disponibles són: {available_choices:s}",
|
||||
"global_settings_bad_choice_for_enum": "Opció pel paràmetre {setting:s} incorrecta, s'ha rebut «{choice:s}», però les opcions disponibles són: {available_choices:s}",
|
||||
"global_settings_bad_type_for_setting": "El tipus del paràmetre {setting:s} és incorrecte. S'ha rebut {received_type:s}, però s'esperava {expected_type:s}",
|
||||
"global_settings_cant_open_settings": "No s'ha pogut obrir el fitxer de configuració, raó: {reason:s}",
|
||||
"global_settings_cant_serialize_settings": "No s'ha pogut serialitzar les dades de configuració, raó: {reason:s}",
|
||||
"global_settings_cant_write_settings": "No s'ha pogut escriure el fitxer de configuració, raó: {reason:s}",
|
||||
"global_settings_key_doesnt_exists": "La clau « {settings_key:s} » no existeix en la configuració global, podeu veure totes les claus disponibles executant « yunohost settings list »",
|
||||
"global_settings_reset_success": "Èxit. S'ha fet una còpia de seguretat de la configuració anterior a {path:s}",
|
||||
"global_settings_reset_success": "S'ha fet una còpia de seguretat de la configuració anterior a {path:s}",
|
||||
"global_settings_setting_example_bool": "Exemple d'opció booleana",
|
||||
"global_settings_setting_example_enum": "Exemple d'opció de tipus enumeració",
|
||||
"global_settings_setting_example_int": "Exemple d'opció de tipus enter",
|
||||
"global_settings_setting_example_string": "Exemple d'opció de tipus cadena",
|
||||
"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_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:s}», refusant-la i guardant-la a /etc/yunohost/settings-unknown.json",
|
||||
"global_settings_unknown_setting_from_settings_file": "Clau de configuració desconeguda: «{setting_key:s}», 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:s} sembla tenir el tipus {unknown_type:s} 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).",
|
||||
"hook_exec_failed": "No s'ha pogut executar l'script: {path:s}",
|
||||
"hook_exec_not_terminated": "L'execució de l'script « {path:s} » no s'ha acabat correctament",
|
||||
"hook_json_return_error": "No s'ha pogut llegir el retorn de l'script {path:s}. Error: {msg:s}. Contingut en brut: {raw_content}",
|
||||
"hook_list_by_invalid": "Propietat per llistar les accions invàlida",
|
||||
"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:s}",
|
||||
"hook_exec_not_terminated": "El script no s'ha acabat correctament: {path:s}",
|
||||
"hook_json_return_error": "No s'ha pogut llegir el retorn del script {path:s}. Error: {msg:s}. Contingut en brut: {raw_content}",
|
||||
"hook_list_by_invalid": "Aquesta propietat no es pot utilitzar per llistar els hooks",
|
||||
"hook_name_unknown": "Nom de script « {name:s} » desconegut",
|
||||
"installation_complete": "Instal·lació completada",
|
||||
"installation_failed": "Ha fallat la instal·lació",
|
||||
"installation_failed": "Ha fallat alguna cosa amb la instal·lació",
|
||||
"invalid_url_format": "Format d'URL invàlid",
|
||||
"ip6tables_unavailable": "No podeu modificar les ip6tables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció",
|
||||
"iptables_unavailable": "No podeu modificar les iptables aquí. O bé sou en un contenidor o bé el vostre nucli no és compatible amb aquesta opció",
|
||||
"log_corrupted_md_file": "El fitxer de metadades yaml associat amb els registres està malmès: « {md_file} »",
|
||||
"log_corrupted_md_file": "El fitxer de metadades YAML associat amb els registres està malmès: « {md_file} »\nError: {error}",
|
||||
"log_category_404": "La categoria de registres « {category} » no existeix",
|
||||
"log_link_to_log": "El registre complet d'aquesta operació: «<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>»",
|
||||
"log_help_to_get_log": "Per veure el registre de l'operació « {desc} », utilitzeu l'ordre « yunohost log display {name} »",
|
||||
"log_link_to_failed_log": "L'operació « {dec} » ha fallat! Per obtenir ajuda, <a href=\"#/tools/logs/{name}\">proveïu el registre complete de l'operació clicant aquí</a>",
|
||||
"log_help_to_get_failed_log": "L'operació « {dec} » ha fallat! Per obtenir ajuda, compartiu el registre complete de l'operació utilitzant l'ordre « yunohost log display {name} --share »",
|
||||
"log_link_to_failed_log": "No s'ha pogut completar l'operació « {desc} ». Per obtenir ajuda, <a href=\"#/tools/logs/{name}\">proveïu el registre complete de l'operació clicant aquí</a>",
|
||||
"log_help_to_get_failed_log": "No s'ha pogut completar l'operació « {desc} ». Per obtenir ajuda, compartiu el registre complete de l'operació utilitzant l'ordre « yunohost log display {name} --share »",
|
||||
"log_does_exists": "No hi ha cap registre per l'operació amb el nom« {log} », utilitzeu « yunohost log list » per veure tots els registre d'operació disponibles",
|
||||
"log_operation_unit_unclosed_properly": "L'operació no s'ha tancat de forma correcta",
|
||||
"log_app_addaccess": "Afegir accés a « {} »",
|
||||
|
@ -263,7 +263,7 @@
|
|||
"log_domain_remove": "Elimina el domini « {} » de la configuració del sistema",
|
||||
"log_dyndns_subscribe": "Subscriure's a un subdomini YunoHost « {} »",
|
||||
"log_dyndns_update": "Actualitza la IP associada al subdomini YunoHost « {} »",
|
||||
"log_letsencrypt_cert_install": "Instal·la el certificat Let's Encrypt al domini « {} »",
|
||||
"log_letsencrypt_cert_install": "Instal·la un certificat Let's Encrypt al domini « {} »",
|
||||
"log_selfsigned_cert_install": "Instal·la el certificat autosignat al domini « {} »",
|
||||
"log_letsencrypt_cert_renew": "Renova el certificat Let's Encrypt de « {} »",
|
||||
"log_service_enable": "Activa el servei « {} »",
|
||||
|
@ -271,88 +271,88 @@
|
|||
"log_user_create": "Afegeix l'usuari « {} »",
|
||||
"log_user_delete": "Elimina l'usuari « {} »",
|
||||
"log_user_update": "Actualitza la informació de l'usuari « {} »",
|
||||
"log_tools_maindomain": "Fes de « {} » el domini principal",
|
||||
"log_domain_main_domain": "Fes de « {} » el domini principal",
|
||||
"log_tools_migrations_migrate_forward": "Migrar",
|
||||
"log_tools_migrations_migrate_backward": "Migrar endarrera",
|
||||
"log_tools_postinstall": "Fer la post instal·lació del servidor YunoHost",
|
||||
"log_tools_upgrade": "Actualitza els paquets del sistema",
|
||||
"log_tools_shutdown": "Apaga el servidor",
|
||||
"log_tools_reboot": "Reinicia el servidor",
|
||||
"already_up_to_date": "No hi ha res a fer! Tot està al dia!",
|
||||
"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)",
|
||||
"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)",
|
||||
"ldap_init_failed_to_create_admin": "La inicialització de LDAP no ha pogut crear l'usuari admin",
|
||||
"ldap_initialized": "S'ha iniciat LDAP",
|
||||
"license_undefined": "indefinit",
|
||||
"mail_alias_remove_failed": "No s'han pogut eliminar els alias del correu «{mail:s}»",
|
||||
"mail_domain_unknown": "Domini d'adreça de correu «{domain:s}» desconegut",
|
||||
"mail_alias_remove_failed": "No s'han pogut eliminar els àlies del correu «{mail:s}»",
|
||||
"mail_domain_unknown": "El domini «{domain:s}» 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:s}»",
|
||||
"mailbox_used_space_dovecot_down": "S'ha d'engegar el servei de correu Dovecot per poder obtenir l'espai utilitzat per la bústia de correu",
|
||||
"mail_unavailable": "Aquesta adreça de correu esta reservada i ha de ser atribuïda automàticament el primer usuari",
|
||||
"maindomain_change_failed": "No s'ha pogut canviar el domini principal",
|
||||
"maindomain_changed": "S'ha canviat el domini principal",
|
||||
"migrate_tsig_end": "La migració cap a hmac-sha512 s'ha acabat",
|
||||
"migrate_tsig_failed": "Ha fallat la migració del domini dyndns {domain} cap a hmac-sha512, anul·lant les modificacions. Error: {error_code} - {error}",
|
||||
"migrate_tsig_start": "L'algoritme de generació de claus no es prou segur per a la signatura TSIG del domini «{domain}», començant la migració cap a un de més segur hmac-sha512",
|
||||
"migrate_tsig_wait": "Esperar 3 minuts per a que el servidor dyndns tingui en compte la nova clau…",
|
||||
"mailbox_used_space_dovecot_down": "S'ha d'engegar el servei de correu Dovecot, per poder obtenir l'espai utilitzat per la bústia de correu",
|
||||
"mail_unavailable": "Aquesta adreça de correu està reservada i ha de ser atribuïda automàticament el primer usuari",
|
||||
"main_domain_change_failed": "No s'ha pogut canviar el domini principal",
|
||||
"main_domain_changed": "S'ha canviat el domini principal",
|
||||
"migrate_tsig_end": "La migració cap a HMAC-SHA-512 s'ha acabat",
|
||||
"migrate_tsig_failed": "Ha fallat la migració del domini DynDNS «{domain}» cap a HMAC-SHA-512, anul·lant les modificacions. Error: {error_code}, {error}",
|
||||
"migrate_tsig_start": "L'algoritme de generació de claus no es prou segur per a la signatura TSIG del domini «{domain}», començant la migració cap a un de més segur HMAC-SHA-512",
|
||||
"migrate_tsig_wait": "Esperant tres minuts per a que el servidor DynDNS tingui en compte la nova clau…",
|
||||
"migrate_tsig_wait_2": "2 minuts…",
|
||||
"migrate_tsig_wait_3": "1 minut…",
|
||||
"migrate_tsig_wait_4": "30 segons…",
|
||||
"migrate_tsig_not_needed": "Sembla que no s'utilitza cap domini dyndns, no és necessari fer cap migració!",
|
||||
"migrate_tsig_not_needed": "Sembla que no s'utilitza cap domini DynDNS, no és necessari fer cap migració.",
|
||||
"migration_description_0001_change_cert_group_to_sslcert": "Canvia els permisos del grup dels certificats de «metronome» a «ssl-cert»",
|
||||
"migration_description_0002_migrate_to_tsig_sha256": "Millora la seguretat de dyndns TSIG utilitzant SHA512 en lloc de MD5",
|
||||
"migration_description_0002_migrate_to_tsig_sha256": "Millora la seguretat de DynDNS TSIG utilitzant SHA-512 en lloc de MD5",
|
||||
"migration_description_0003_migrate_to_stretch": "Actualització del sistema a Debian Stretch i YunoHost 3.0",
|
||||
"migration_description_0004_php5_to_php7_pools": "Tornar a configurar els pools PHP per utilitzar PHP 7 en lloc de PHP 5",
|
||||
"migration_description_0005_postgresql_9p4_to_9p6": "Migració de les bases de dades de postgresql 9.4 a 9.6",
|
||||
"migration_description_0005_postgresql_9p4_to_9p6": "Migració de les bases de dades de PostgreSQL 9.4 a 9.6",
|
||||
"migration_description_0006_sync_admin_and_root_passwords": "Sincronitzar les contrasenyes admin i root",
|
||||
"migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuració SSH serà gestionada per YunoHost (pas 1, automàtic)",
|
||||
"migration_description_0008_ssh_conf_managed_by_yunohost_step2": "La configuració SSH serà gestionada per YunoHost (pas 2, manual)",
|
||||
"migration_description_0009_decouple_regenconf_from_services": "Desvincula el mecanisme regen-conf dels serveis",
|
||||
"migration_description_0010_migrate_to_apps_json": "Elimina la appslists (desfasat) i utilitza la nova llista unificada «apps.json» en el seu lloc",
|
||||
"migration_description_0010_migrate_to_apps_json": "Elimina les llistes d'aplicacions obsoletes i utilitza la nova llista unificada «apps.json» en el seu lloc",
|
||||
"migration_0003_backward_impossible": "La migració Stretch no és reversible.",
|
||||
"migration_0003_start": "Ha començat la migració a Stretch. Els registres estaran disponibles a {logfile}.",
|
||||
"migration_0003_patching_sources_list": "Modificant el fitxer sources.lists…",
|
||||
"migration_0003_main_upgrade": "Començant l'actualització principal…",
|
||||
"migration_0003_fail2ban_upgrade": "Començant l'actualització de fail2ban…",
|
||||
"migration_0003_fail2ban_upgrade": "Començant l'actualització de Fail2Ban…",
|
||||
"migration_0003_restoring_origin_nginx_conf": "El fitxer /etc/nginx/nginx.conf ha estat editat. La migració el tornarà al seu estat original... El fitxer anterior estarà disponible com a {backup_dest}.",
|
||||
"migration_0003_yunohost_upgrade": "Començant l'actualització del paquet yunohost... La migració acabarà, però l'actualització actual es farà just després. Després de completar aquesta operació, pot ser que us hagueu de tornar a connectar a la web d'administració.",
|
||||
"migration_0003_yunohost_upgrade": "Començant l'actualització del paquet YunoHost... La migració acabarà, però l'actualització actual es farà just després. Després de completar aquesta operació, pot ser que us hagueu de tornar a connectar a la web d'administració.",
|
||||
"migration_0003_not_jessie": "La distribució Debian actual no és Jessie!",
|
||||
"migration_0003_system_not_fully_up_to_date": "El vostre sistema no està completament actualitzat. S'ha de fer una actualització normal abans de fer la migració a Stretch.",
|
||||
"migration_0003_still_on_jessie_after_main_upgrade": "Hi ha hagut un problema durant l'actualització principal: el sistema encara està amb Jessie!? Per investigar el problema, mireu el registres a {log}:s…",
|
||||
"migration_0003_general_warning": "Tingueu en compte que la migració és una operació delicada. Tot i que l'equip de YunoHost a fet els possibles per revisar-la i provar-la, la migració pot provocar errors en parts del sistema o aplicacions.\n\nPer tant, recomanem:\n - Fer una còpia de seguretat de les dades o aplicacions importants. Més informació a https://yunohost.org/backup;\n - Sigueu pacient un cop llençada la migració: en funció de la connexió a internet i el maquinari, pot trigar fins a unes hores per actualitzar-ho tot.\n\nD'altra banda, el port per SMTP, utilitzat per clients de correu externs (com Thunderbird o K9-Mail) ha canviat de 465 (SSL/TLS) a 587 (STARTTLS). L'antic port 465 serà tancat automàticament i el nou port 587 serà obert en el tallafocs. Tots els usuaris *hauran* d'adaptar la configuració dels clients de correu en acord amb aquests canvis!",
|
||||
"migration_0003_problematic_apps_warning": "Tingueu en compte que s'han detectat les aplicacions, possiblement, problemàtiques següents. Sembla que aquestes no s'han instal·lat des d'una applist o que no estan marcades com a «working». Per conseqüent, no podem garantir que segueixin funcionant després de l'actualització: {problematic_apps}",
|
||||
"migration_0003_still_on_jessie_after_main_upgrade": "Hi ha hagut un problema durant l'actualització principal: El sistema encara està amb Jessie? Per investigar el problema, mireu el registres a {log}:s…",
|
||||
"migration_0003_general_warning": "Tingueu en compte que la migració és una operació delicada. L'equip de YunoHost a fet els possibles per revisar-la i provar-la, però la migració pot provocar errors en parts del sistema o aplicacions.\n\nPer tant, es recomana:\n - Fer una còpia de seguretat de les dades o aplicacions importants. Més informació a https://yunohost.org/backup;\n - Sigueu pacient un cop llençada la migració: en funció de la connexió a internet i el maquinari, pot trigar fins a unes hores per actualitzar-ho tot.\n\nD'altra banda, el port per SMTP, utilitzat per clients de correu externs (com Thunderbird o K9-Mail) ha canviat de 465 (SSL/TLS) a 587 (STARTTLS). L'antic port (465) serà tancat automàticament, i el nou port (587) serà obert en el tallafocs. Tots els usuaris *hauran* d'adaptar la configuració dels clients de correu en acord amb aquests canvis.",
|
||||
"migration_0003_problematic_apps_warning": "Tingueu en compte que s'han detectat les aplicacions, possiblement, problemàtiques següents. Sembla que aquestes no s'han instal·lat des d'una applist, o que no estan marcades com a «working». Per conseqüent, no podem garantir que segueixin funcionant després de l'actualització: {problematic_apps}",
|
||||
"migration_0003_modified_files": "Tingueu en compte que s'han detectat els següents fitxers que han estat modificats manualment i podrien sobreescriure's al final de l'actualització: {manually_modified_files}",
|
||||
"migration_0005_postgresql_94_not_installed": "Postgresql no està instal·lat en el sistema. No hi ha res per fer!",
|
||||
"migration_0005_postgresql_96_not_installed": "S'ha trobat Postgresql 9.4 instal·lat, però no Postgresql 9.6!? Alguna cosa estranya a passat en el sistema :( …",
|
||||
"migration_0005_not_enough_space": "No hi ha prou espai disponible en {path} per fer la migració en aquest moment :(.",
|
||||
"migration_0006_disclaimer": "YunoHost esperar que les contrasenyes admin i root estiguin sincronitzades. Fent aquesta migració, la contrasenya root serà reemplaçada per la contrasenya admin.",
|
||||
"migration_0007_cancelled": "YunoHost no ha pogut millorar la gestió de la configuració SSH.",
|
||||
"migration_0005_postgresql_94_not_installed": "PostgreSQL no està instal·lat en el sistema. No hi ha res per fer.",
|
||||
"migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 està instal·lat, però no PostgreSQL 9.6? Alguna cosa estranya a passat en el sistema :( …",
|
||||
"migration_0005_not_enough_space": "Creu espai disponible en {path} per executar la migració.",
|
||||
"migration_0006_disclaimer": "YunoHost esperar que les contrasenyes de admin i root estiguin sincronitzades. Aquesta migració canvia la contrasenya root per la contrasenya admin.",
|
||||
"migration_0007_cancelled": "No s'ha pogut millorar la gestió de la configuració SSH.",
|
||||
"migration_0007_cannot_restart": "No es pot reiniciar SSH després d'haver intentat cancel·lar la migració numero 6.",
|
||||
"migration_0008_general_disclaimer": "Per millorar la seguretat del servidor, es recomana que sigui YunoHost qui gestioni la configuració SSH. La configuració SSH actual és diferent a la configuració recomanada. Si deixeu que YunoHost ho reconfiguri, la manera de connectar-se al servidor mitjançant SSH canviarà de la següent manera:",
|
||||
"migration_0008_port": " - la connexió es farà utilitzant el port 22 en lloc del port SSH personalitzat actual. Es pot reconfigurar;",
|
||||
"migration_0008_root": " - no es podrà connectar com a root a través de SSH. S'haurà d'utilitzar l'usuari admin per fer-ho;",
|
||||
"migration_0008_dsa": " - es desactivarà la clau DSA. Per tant, es podria haver d'invalidar un missatge esgarrifós del client SSH, i tornar a verificar l'empremta digital del servidor;",
|
||||
"migration_0008_warning": "Si heu entès els avisos i accepteu que YunoHost sobreescrigui la configuració actual, comenceu la migració. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.",
|
||||
"migration_0008_no_warning": "No s'han detectat riscs importants per sobreescriure la configuració SSH, però no es pot estar del tot segur ;)! Si accepteu que YunoHost sobreescrigui la configuració actual, comenceu la migració. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.",
|
||||
"migration_0009_not_needed": "Sembla que ja s'ha fet aquesta migració? Ometent.",
|
||||
"migration_0008_port": "• La connexió es farà utilitzant el port 22 en lloc del port SSH personalitzat actual. Es pot reconfigurar;",
|
||||
"migration_0008_root": "• No es podrà connectar com a root a través de SSH. S'haurà d'utilitzar l'usuari admin per fer-ho;",
|
||||
"migration_0008_dsa": "• Es desactivarà la clau DSA. Per tant, es podria haver d'invalidar un missatge esgarrifós del client SSH, i tornar a verificar l'empremta digital del servidor;",
|
||||
"migration_0008_warning": "Si heu entès els avisos i voleu que YunoHost sobreescrigui la configuració actual, comenceu la migració. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.",
|
||||
"migration_0008_no_warning": "Hauria de ser segur sobreescriure la configuració SSH, però no es pot estar del tot segur! Executetu la migració per sobreescriure-la. Sinó, podeu saltar-vos la migració, tot i que no està recomanat.",
|
||||
"migration_0009_not_needed": "Sembla que ja s'ha fet aquesta migració… (?) Ometent.",
|
||||
"migrations_backward": "Migració cap enrere.",
|
||||
"migrations_bad_value_for_target": "Nombre invàlid pel paràmetre target, els nombres de migració disponibles són 0 o {}",
|
||||
"migrations_cant_reach_migration_file": "No s'ha pogut accedir als fitxers de migració al camí %s",
|
||||
"migrations_cant_reach_migration_file": "No s'ha pogut accedir als fitxers de migració al camí «%s»",
|
||||
"migrations_current_target": "La migració objectiu és {}",
|
||||
"migrations_error_failed_to_load_migration": "ERROR: no s'ha pogut carregar la migració {number} {name}",
|
||||
"migrations_forward": "Migració endavant",
|
||||
"migrations_list_conflict_pending_done": "No es pot utilitzar --previous i --done al mateix temps.",
|
||||
"migrations_loading_migration": "Carregant la migració {number} {name}…",
|
||||
"migrations_migration_has_failed": "La migració {number} {name} ha fallat amb l'excepció {exception}, cancel·lant",
|
||||
"migrations_list_conflict_pending_done": "No es pot utilitzar «--previous» i «--done» al mateix temps.",
|
||||
"migrations_loading_migration": "Carregant la migració {id}…",
|
||||
"migrations_migration_has_failed": "La migració {id} ha fallat, cancel·lant. Error: {exception}",
|
||||
"migrations_no_migrations_to_run": "No hi ha cap migració a fer",
|
||||
"migrations_show_currently_running_migration": "Fent la migració {number} {name}…",
|
||||
"migrations_show_last_migration": "L'última migració feta és {}",
|
||||
"migrations_skip_migration": "Saltant migració {number} {name}…",
|
||||
"migrations_skip_migration": "Saltant migració {id}…",
|
||||
"migrations_success": "S'ha completat la migració {number} {name} amb èxit!",
|
||||
"migrations_to_be_ran_manually": "La migració {number} {name} s'ha de fer manualment. Aneu a Eines > Migracions a la interfície admin, o executeu «yunohost tools migrations migrate».",
|
||||
"migrations_need_to_accept_disclaimer": "Per fer la migració {number} {name}, heu d'acceptar aquesta clàusula de no responsabilitat:\n---\n{disclaimer}\n---\nSi accepteu fer la migració, torneu a executar l'ordre amb l'opció --accept-disclaimer.",
|
||||
"monitor_disabled": "El monitoratge del servidor ha estat desactivat",
|
||||
"monitor_enabled": "El monitoratge del servidor ha estat activat",
|
||||
"migrations_to_be_ran_manually": "La migració {id} s'ha de fer manualment. Aneu a Eines → Migracions a la interfície admin, o executeu «yunohost tools migrations migrate».",
|
||||
"migrations_need_to_accept_disclaimer": "Per fer la migració {id}, heu d'acceptar aquesta clàusula de no responsabilitat:\n---\n{disclaimer}\n---\nSi accepteu fer la migració, torneu a executar l'ordre amb l'opció «--accept-disclaimer».",
|
||||
"monitor_disabled": "S'ha desactivat el monitoratge del servidor",
|
||||
"monitor_enabled": "S'ha activat el monitoratge del sistema",
|
||||
"monitor_glances_con_failed": "No s'ha pogut connectar al servidor Glances",
|
||||
"monitor_not_enabled": "El monitoratge del servidor no està activat",
|
||||
"monitor_period_invalid": "Període de temps invàlid",
|
||||
|
@ -384,18 +384,18 @@
|
|||
"pattern_firstname": "Ha de ser un nom vàlid",
|
||||
"pattern_lastname": "Ha de ser un cognom vàlid",
|
||||
"pattern_listname": "Ha d'estar compost per caràcters alfanumèrics i guió baix exclusivament",
|
||||
"pattern_mailbox_quota": "Ha de ser una mida amb el sufix b/k/M/G/T o 0 per desactivar la quota",
|
||||
"pattern_mailbox_quota": "Ha de ser una mida amb el sufix b/k/M/G/T o 0 per no tenir quota",
|
||||
"pattern_password": "Ha de tenir un mínim de 3 caràcters",
|
||||
"pattern_port": "Ha de ser un número de port vàlid (i.e. 0-65535)",
|
||||
"pattern_port_or_range": "Ha de ser un número de port vàlid (i.e. 0-65535) o un interval de ports (ex. 100:200)",
|
||||
"pattern_positive_number": "Ha de ser un nombre positiu",
|
||||
"pattern_username": "Ha d'estar compost per caràcters alfanumèrics en minúscula i guió baix exclusivament",
|
||||
"pattern_password_app": "Les contrasenyes no haurien de tenir els següents caràcters: {forbidden_chars}",
|
||||
"pattern_password_app": "Les contrasenyes no poden de tenir els següents caràcters: {forbidden_chars}",
|
||||
"port_already_closed": "El port {port:d} ja està tancat per les connexions {ip_version:s}",
|
||||
"port_already_opened": "El port {port:d} ja està obert per les connexions {ip_version:s}",
|
||||
"port_available": "El port {port:d} està disponible",
|
||||
"port_unavailable": "El port {port:d} no està disponible",
|
||||
"recommend_to_add_first_user": "La post instal·lació s'ha acabat, però YunoHost necessita com a mínim un usuari per funcionar correctament, hauríeu d'afegir un usuari executant «yunohost user create $username» o amb la interfície d'administració.",
|
||||
"recommend_to_add_first_user": "La post instal·lació s'ha acabat, però YunoHost necessita com a mínim un usuari per funcionar correctament, hauríeu d'afegir un usuari executant «yunohost user create <username>»; o fer-ho des de la interfície d'administració.",
|
||||
"regenconf_file_backed_up": "S'ha guardat una còpia de seguretat del fitxer de configuració «{conf}» a «{backup}»",
|
||||
"regenconf_file_copy_failed": "No s'ha pogut copiar el nou fitxer de configuració «{new}» a «{conf}»",
|
||||
"regenconf_file_kept_back": "S'espera que el fitxer de configuració «{conf}» sigui suprimit per regen-conf (categoria {category}) però s'ha mantingut.",
|
||||
|
@ -406,26 +406,26 @@
|
|||
"regenconf_file_updated": "El fitxer de configuració «{conf}» ha estat actualitzat",
|
||||
"regenconf_now_managed_by_yunohost": "El fitxer de configuració «{conf}» serà gestionat per YunoHost a partir d'ara (categoria {category}).",
|
||||
"regenconf_up_to_date": "La configuració ja està al dia per la categoria «{category}»",
|
||||
"regenconf_updated": "La configuració ha estat actualitzada per la categoria «{category}»",
|
||||
"regenconf_updated": "La configuració per la categoria «{category}» ha estat actualitzada",
|
||||
"regenconf_would_be_updated": "La configuració hagués estat actualitzada per la categoria «{category}»",
|
||||
"regenconf_dry_pending_applying": "Verificació de la configuració pendent que s'hauria d'haver aplicat per la categoria «{category}»…",
|
||||
"regenconf_failed": "No s'ha pogut regenerar la configuració per la/les categoria/es : {categories}",
|
||||
"regenconf_pending_applying": "Aplicació de la configuració pendent per la categoria «{category}»…",
|
||||
"restore_action_required": "S'ha d'especificar quelcom a restaurar",
|
||||
"restore_already_installed_app": "Ja hi ha una aplicació instal·lada amb l'id «{app:s}»",
|
||||
"restore_already_installed_app": "Una aplicació amb la ID «{app:s}» ja està instal·lada",
|
||||
"restore_app_failed": "No s'ha pogut restaurar l'aplicació «{app:s}»",
|
||||
"restore_cleaning_failed": "No s'ha pogut netejar el directori temporal de restauració",
|
||||
"restore_complete": "Restauració completada",
|
||||
"restore_confirm_yunohost_installed": "Esteu segur de voler restaurar un sistema ja instal·lat? [{answers:s}]",
|
||||
"restore_extracting": "Extracció dels fitxers necessaris de l'arxiu…",
|
||||
"restore_failed": "No s'ha pogut restaurar el sistema",
|
||||
"restore_hook_unavailable": "L'script de restauració «{part:s}» no està disponible en el sistema i tampoc és en l'arxiu",
|
||||
"restore_may_be_not_enough_disk_space": "Sembla que no hi ha prou espai disponible en el disc (espai lliure: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)",
|
||||
"restore_hook_unavailable": "El script de restauració «{part:s}» no està disponible en el sistema i tampoc és en l'arxiu",
|
||||
"restore_may_be_not_enough_disk_space": "Sembla que no hi ha prou espai disponible en el sistema (lliure: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)",
|
||||
"restore_mounting_archive": "Muntatge de l'arxiu a «{path:s}»",
|
||||
"restore_not_enough_disk_space": "No hi ha prou espai disponible en el disc (espai lliure: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)",
|
||||
"restore_not_enough_disk_space": "No hi ha prou espai disponible (espai: {free_space:d} B, espai necessari: {needed_space:d} B, marge de seguretat: {margin:d} B)",
|
||||
"restore_nothings_done": "No s'ha restaurat res",
|
||||
"restore_removing_tmp_dir_failed": "No s'ha pogut eliminar un directori temporal antic",
|
||||
"restore_running_app_script": "Execució de l'script de restauració de l'aplicació «{app:s}»…",
|
||||
"restore_running_app_script": "Restaurant l'aplicació «{app:s}»…",
|
||||
"restore_running_hooks": "Execució dels hooks de restauració…",
|
||||
"restore_system_part_failed": "No s'ha pogut restaurar la part «{part:s}» del sistema",
|
||||
"root_password_desynchronized": "S'ha canviat la contrasenya d'administració, però YunoHost no ha pogut propagar-ho cap a la contrasenya root!",
|
||||
|
@ -436,31 +436,31 @@
|
|||
"server_reboot_confirm": "Es reiniciarà el servidor immediatament, n'esteu segur? [{answers:s}]",
|
||||
"service_add_failed": "No s'ha pogut afegir el servei «{service:s}»",
|
||||
"service_added": "S'ha afegit el servei «{service:s}»",
|
||||
"service_already_started": "Ja s'ha iniciat el servei «{service:s}»",
|
||||
"service_already_started": "El servei «{service:s}» ja està funcionant",
|
||||
"service_already_stopped": "Ja s'ha aturat el servei «{service:s}»",
|
||||
"service_cmd_exec_failed": "No s'ha pogut executar l'ordre «{command:s}»",
|
||||
"service_description_avahi-daemon": "permet accedir al servidor via yunohost.local en la xarxa local",
|
||||
"service_description_dnsmasq": "gestiona la resolució del nom de domini (DNS)",
|
||||
"service_description_dovecot": "permet als clients de correu accedir/recuperar correus (via IMAP i POP3)",
|
||||
"service_description_fail2ban": "protegeix contra els atacs de força bruta i a altres atacs provinents d'Internet",
|
||||
"service_description_glances": "monitora la informació del sistema en el servidor",
|
||||
"service_description_metronome": "gestiona els comptes de missatgeria instantània XMPP",
|
||||
"service_description_mysql": "guarda les dades de les aplicacions (base de dades SQL)",
|
||||
"service_description_nginx": "serveix o permet l'accés a totes les pàgines web allotjades en el servidor",
|
||||
"service_description_nslcd": "gestiona les connexions shell dels usuaris YunoHost",
|
||||
"service_description_php7.0-fpm": "executa les aplicacions escrites en PHP amb nginx",
|
||||
"service_description_postfix": "utilitzat per enviar i rebre correus",
|
||||
"service_description_redis-server": "una base de dades especialitzada per l'accés ràpid a dades, files d'espera i comunicació entre programes",
|
||||
"service_description_rmilter": "verifica diferents paràmetres en els correus",
|
||||
"service_description_rspamd": "filtra el correu brossa, i altres funcionalitats relacionades al correu",
|
||||
"service_description_slapd": "guarda el usuaris, dominis i informació relacionada",
|
||||
"service_description_ssh": "permet la connexió remota al servidor via terminal (protocol SSH)",
|
||||
"service_description_yunohost-api": "gestiona les interaccions entre la interfície web de YunoHost i el sistema",
|
||||
"service_description_yunohost-firewall": "gestiona els ports de connexió oberts i tancats als serveis",
|
||||
"service_description_avahi-daemon": "Permet accedir al servidor via «yunohost.local» en la xarxa local",
|
||||
"service_description_dnsmasq": "Gestiona la resolució del nom de domini (DNS)",
|
||||
"service_description_dovecot": "Permet als clients de correu accedir/recuperar correus (via IMAP i POP3)",
|
||||
"service_description_fail2ban": "Protegeix contra els atacs de força bruta i a altres atacs provinents d'Internet",
|
||||
"service_description_glances": "Monitora la informació del sistema en el servidor",
|
||||
"service_description_metronome": "Gestiona els comptes de missatgeria instantània XMPP",
|
||||
"service_description_mysql": "Guarda les dades de les aplicacions (base de dades SQL)",
|
||||
"service_description_nginx": "Serveix o permet l'accés a totes les pàgines web allotjades en el servidor",
|
||||
"service_description_nslcd": "Gestiona les connexions shell dels usuaris YunoHost",
|
||||
"service_description_php7.0-fpm": "Executa les aplicacions escrites en PHP amb NGINX",
|
||||
"service_description_postfix": "Utilitzat per enviar i rebre correus",
|
||||
"service_description_redis-server": "Una base de dades especialitzada per l'accés ràpid a dades, files d'espera i comunicació entre programes",
|
||||
"service_description_rmilter": "Verifica diferents paràmetres en els correus",
|
||||
"service_description_rspamd": "Filtra el correu brossa, i altres funcionalitats relacionades amb el correu",
|
||||
"service_description_slapd": "Guarda el usuaris, dominis i informació relacionada",
|
||||
"service_description_ssh": "Permet la connexió remota al servidor via terminal (protocol SSH)",
|
||||
"service_description_yunohost-api": "Gestiona les interaccions entre la interfície web de YunoHost i el sistema",
|
||||
"service_description_yunohost-firewall": "Gestiona els ports de connexió oberts i tancats als serveis",
|
||||
"service_disable_failed": "No s'han pogut deshabilitar el servei «{service:s}»\n\nRegistres recents: {logs:s}",
|
||||
"service_disabled": "S'ha deshabilitat el servei {service:s}",
|
||||
"service_disabled": "S'ha deshabilitat el servei «{service:s}»",
|
||||
"service_enable_failed": "No s'ha pogut activar el servei «{service:s}»\n\nRegistres recents: {log:s}",
|
||||
"service_enabled": "S'ha activat el servei {service:s}",
|
||||
"service_enabled": "S'ha activat el servei «{service:s}»",
|
||||
"service_no_log": "No hi ha cap registre pel servei «{service:s}»",
|
||||
"service_regen_conf_is_deprecated": "«yunohost service regen-conf» està desfasat! Utilitzeu «yunohost tools regen-conf» en el seu lloc.",
|
||||
"service_remove_failed": "No s'ha pogut eliminar el servei «{service:s}»",
|
||||
|
@ -479,26 +479,26 @@
|
|||
"service_unknown": "Servei «{service:s}» desconegut",
|
||||
"ssowat_conf_generated": "S'ha generat la configuració SSOwat",
|
||||
"ssowat_conf_updated": "S'ha actualitzat la configuració SSOwat",
|
||||
"ssowat_persistent_conf_read_error": "Error en llegir la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON",
|
||||
"ssowat_persistent_conf_write_error": "Error guardant la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON",
|
||||
"ssowat_persistent_conf_read_error": "No s'ha pogut llegir la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON",
|
||||
"ssowat_persistent_conf_write_error": "No s'ha pogut guardar la configuració persistent de SSOwat: {error:s}. Modifiqueu el fitxer /etc/ssowat/conf.json.persistent per arreglar la sintaxi JSON",
|
||||
"system_upgraded": "S'ha actualitzat el sistema",
|
||||
"system_username_exists": "El nom d'usuari ja existeix en els usuaris de sistema",
|
||||
"this_action_broke_dpkg": "Aquesta acció a trencat dpkg/apt (els gestors de paquets del sistema)… Podeu intentar resoldre el problema connectant-vos amb SSH i executant «sudo dpkg --configure -a».",
|
||||
"tools_upgrade_at_least_one": "Especifiqueu --apps O --system",
|
||||
"system_username_exists": "El nom d'usuari ja existeix en la llista d'usuaris de sistema",
|
||||
"this_action_broke_dpkg": "Aquesta acció a trencat dpkg/APT (els gestors de paquets del sistema)… Podeu intentar resoldre el problema connectant-vos amb SSH i executant «sudo dpkg --configure -a».",
|
||||
"tools_upgrade_at_least_one": "Especifiqueu «--apps», o «--system»",
|
||||
"tools_upgrade_cant_both": "No es poden actualitzar tant el sistema com les aplicacions al mateix temps",
|
||||
"tools_upgrade_cant_hold_critical_packages": "No es poden mantenir els paquets crítics…",
|
||||
"tools_upgrade_cant_unhold_critical_packages": "No es poden deixar de mantenir els paquets crítics…",
|
||||
"tools_upgrade_regular_packages": "Actualitzant els paquets «normals» (no relacionats amb YunoHost)…",
|
||||
"tools_upgrade_regular_packages_failed": "No s'han pogut actualitzar els paquets següents: {packages_list}",
|
||||
"tools_upgrade_special_packages": "Actualitzant els paquets «especials» (relacionats amb YunoHost)…",
|
||||
"tools_upgrade_special_packages_explanation": "Aquesta acció s'acabarà, però l'actualització especial continuarà en segon pla. No comenceu cap altra acció al servidor en els pròxims ~10 minuts (depèn de la velocitat del maquinari). Un cop acabat, pot ser que us hagueu de tornar a connectar a la interfície d'administració. Els registres de l'actualització estaran disponibles a Eines > Registres (a la interfície d'administració) o amb «yunohost log list» (a la línia d'ordres).",
|
||||
"tools_upgrade_special_packages_completed": "Actualització dels paquets YunoHost acabada!\nPremeu [Enter] per tornar a la línia d'ordres",
|
||||
"tools_upgrade_special_packages_explanation": "Aquesta acció s'acabarà, però l'actualització especial continuarà en segon pla. No comenceu cap altra acció al servidor en els pròxims ~10 minuts (depèn de la velocitat del maquinari). Un cop acabat, pot ser que us hagueu de tornar a connectar a la interfície d'administració. Els registres de l'actualització estaran disponibles a Eines → Registres (a la interfície d'administració) o amb «yunohost log list» (des de la línia d'ordres).",
|
||||
"tools_upgrade_special_packages_completed": "Actualització dels paquets YunoHost acabada.\nPremeu [Enter] per tornar a la línia d'ordres",
|
||||
"unbackup_app": "L'aplicació «{app:s}» no serà guardada",
|
||||
"unexpected_error": "Hi ha hagut un error inesperat: {error}",
|
||||
"unit_unknown": "Unitat desconeguda «{unit:s}»",
|
||||
"unlimit": "Sense quota",
|
||||
"unrestore_app": "L'aplicació «{app:s} no serà restaurada",
|
||||
"update_apt_cache_failed": "No s'ha pogut actualitzar la memòria cau d'APT (el gestor de paquets de Debian). Aquí teniu les línies de sources.list que poden ajudar-vos a identificar les línies problemàtiques:\n{sourceslist}",
|
||||
"update_apt_cache_failed": "No s'ha pogut actualitzar la memòria cau d'APT (el gestor de paquets de Debian). Aquí teniu les línies de sources.list, que poden ajudar-vos a identificar les línies problemàtiques:\n{sourceslist}",
|
||||
"update_apt_cache_warning": "Hi ha hagut errors al actualitzar la memòria cau d'APT (el gestor de paquets de Debian). Aquí teniu les línies de sources.list que poden ajudar-vos a identificar les línies problemàtiques:\n{sourceslist}",
|
||||
"updating_apt_cache": "Obtenció de les actualitzacions disponibles per als paquets del sistema…",
|
||||
"updating_app_lists": "Obtenció de les actualitzacions disponibles per a les aplicacions…",
|
||||
|
@ -507,21 +507,125 @@
|
|||
"upnp_dev_not_found": "No s'ha trobat cap dispositiu UPnP",
|
||||
"upnp_disabled": "S'ha desactivat UPnP",
|
||||
"upnp_enabled": "S'ha activat UPnP",
|
||||
"upnp_port_open_failed": "No s'han pogut obrir els ports UPnP",
|
||||
"upnp_port_open_failed": "No s'ha pogut obrir el port UPnP",
|
||||
"user_created": "S'ha creat l'usuari",
|
||||
"user_creation_failed": "No s'ha pogut crear l'usuari",
|
||||
"user_creation_failed": "No s'ha pogut crear l'usuari {user}: {error}",
|
||||
"user_deleted": "S'ha suprimit l'usuari",
|
||||
"user_deletion_failed": "No s'ha pogut suprimir l'usuari",
|
||||
"user_home_creation_failed": "No s'ha pogut crear la carpeta personal («home») de l'usuari",
|
||||
"user_deletion_failed": "No s'ha pogut suprimir l'usuari {user}: {error}",
|
||||
"user_home_creation_failed": "No s'ha pogut crear la carpeta personal «home» per l'usuari",
|
||||
"user_info_failed": "No s'ha pogut obtenir la informació de l'usuari",
|
||||
"user_unknown": "Usuari desconegut: {user:s}",
|
||||
"user_update_failed": "No s'ha pogut actualitzar l'usuari",
|
||||
"user_updated": "S'ha actualitzat l'usuari",
|
||||
"user_update_failed": "No s'ha pogut actualitzar l'usuari {user}: {error}",
|
||||
"user_updated": "S'ha canviat la informació de l'usuari",
|
||||
"users_available": "Usuaris disponibles:",
|
||||
"yunohost_already_installed": "YunoHost ja està instal·lat",
|
||||
"yunohost_ca_creation_failed": "No s'ha pogut crear l'autoritat de certificació",
|
||||
"yunohost_ca_creation_success": "S'ha creat l'autoritat de certificació local.",
|
||||
"yunohost_configured": "S'ha configurat YunoHost",
|
||||
"yunohost_configured": "YunoHost està configurat",
|
||||
"yunohost_installing": "Instal·lació de YunoHost…",
|
||||
"yunohost_not_installed": "YunoHost no està instal·lat o no està instal·lat correctament. Executeu «yunohost tools postinstall»"
|
||||
"yunohost_not_installed": "YunoHost no està instal·lat correctament. Executeu «yunohost tools postinstall»",
|
||||
"apps_permission_not_found": "No s'ha trobat cap permís per les aplicacions instal·lades",
|
||||
"apps_permission_restoration_failed": "Ha fallat el permís «{permission:s}» per la restauració de l'aplicació {app:s}",
|
||||
"backup_permission": "Permís de còpia de seguretat per l'aplicació {app:s}",
|
||||
"edit_group_not_allowed": "No teniu autorització per modificar el grup {group:s}",
|
||||
"edit_permission_with_group_all_users_not_allowed": "No podeu modificar els permisos del grup «all_users», s'ha d'utlilitzar «yunohost user permission clear APP» o «yunohost user permission add APP -u USER».",
|
||||
"error_when_removing_sftpuser_group": "Error intentant eliminar el gruo sftpusers",
|
||||
"group_already_allowed": "El grup «{group:s}» ja té el permís «{permission:s}» activat per l'aplicació «{app:s}»",
|
||||
"group_already_disallowed": "El grup «{group:s}» ja té els permisos «{permission:s}» desactivats per l'aplicació «{app:s}»",
|
||||
"group_name_already_exist": "El grup {name:s} ja existeix",
|
||||
"group_created": "S'ha creat el grup «{group}»",
|
||||
"group_creation_failed": "No s'ha pogut crear el grup «{group}»: {error}",
|
||||
"group_deleted": "S'ha eliminat el grup «{group}»",
|
||||
"group_deletion_failed": "No s'ha pogut eliminar el grup «{group}»: {error}",
|
||||
"group_deletion_not_allowed": "El grup {group:s} no es pot eliminar manualment.",
|
||||
"group_info_failed": "Ha fallat la informació del grup",
|
||||
"group_unknown": "Grup {group:s} desconegut",
|
||||
"group_updated": "S'ha actualitzat el grup «{group}»",
|
||||
"group_update_failed": "No s'ha pogut actualitzat el grup «{group}»: {error}",
|
||||
"log_permission_add": "Afegir el permís «{}» per l'aplicació «{}»",
|
||||
"log_permission_remove": "Suprimir el permís «{}»",
|
||||
"log_permission_update": "Actualitzar el permís «{}» per l'aplicació «{}»",
|
||||
"log_user_group_add": "Afegir grup «{}»",
|
||||
"log_user_group_delete": "Eliminar grup «{}»",
|
||||
"log_user_group_update": "Actualitzar grup «{}»",
|
||||
"log_user_permission_add": "Actualitzar el permís «{}»",
|
||||
"log_user_permission_remove": "Actualitzar el permís «{}»",
|
||||
"mailbox_disabled": "La bústia de correu està desactivada per al usuari {user:s}",
|
||||
"migration_description_0011_setup_group_permission": "Configurar el grup d'usuaris i els permisos per les aplicacions i els serveis",
|
||||
"migration_0011_backup_before_migration": "Creant una còpia de seguretat de la base de dades LDAP i la configuració de les aplicacions abans d'efectuar la migració.",
|
||||
"migration_0011_can_not_backup_before_migration": "No s'ha pogut completar la còpia de seguretat abans de que la migració fallés. Error: {error:s}",
|
||||
"migration_0011_create_group": "Creant un grup per a cada usuari…",
|
||||
"migration_0011_done": "Migració completada. Ja podeu gestionar grups d'usuaris.",
|
||||
"migration_0011_LDAP_config_dirty": "Sembla que heu modificat manualment la configuració LDAP. Per fer aquesta migració s'ha d'actualitzar la configuració LDAP.\nGuardeu la configuració actual, reinicieu la configuració original executant l'ordre «yunohost tools regen-conf -f» i torneu a intentar la migració",
|
||||
"migration_0011_LDAP_update_failed": "Ha fallat l'actualització de LDAP. Error: {error:s}",
|
||||
"migration_0011_migrate_permission": "Fent la migració dels permisos de la configuració de les aplicacions a LDAP…",
|
||||
"migration_0011_migration_failed_trying_to_rollback": "No s'ha pogut fer la migració… s'intenta tornar el sistema a l'estat anterior.",
|
||||
"migration_0011_rollback_success": "S'ha tornat el sistema a l'estat anterior.",
|
||||
"migration_0011_update_LDAP_database": "Actualitzant la base de dades LDAP…",
|
||||
"migration_0011_update_LDAP_schema": "Actualitzant l'esquema LDAP…",
|
||||
"need_define_permission_before": "Heu de tornar a redefinir els permisos utilitzant «yunohost user permission add -u USER» abans d'eliminar un grup permès",
|
||||
"permission_already_clear": "Ja s'ha donat el permís «{permission:s}» per l'aplicació {app:s}",
|
||||
"permission_already_exist": "El permís «{permission:s}» ja existeix",
|
||||
"permission_created": "S'ha creat el permís «{permission:s}»",
|
||||
"permission_creation_failed": "No s'ha pogut crear el permís «{permission}»: {error}",
|
||||
"permission_deleted": "S'ha eliminat el permís «{permission:s}»",
|
||||
"permission_deletion_failed": "No s'ha pogut eliminar el permís «{permission:s}»: {error}",
|
||||
"permission_not_found": "No s'ha trobat el permís «{permission:s}»",
|
||||
"permission_name_not_valid": "El nom del permís «{permission:s}» no és vàlid",
|
||||
"permission_update_failed": "No s'ha pogut actualitzar el permís «{permission}»: {error}",
|
||||
"permission_generated": "S'ha actualitzat la base de dades del permís",
|
||||
"permission_updated": "S'ha actualitzat el permís «{permission:s}»",
|
||||
"permission_update_nothing_to_do": "No hi ha cap permís per actualitzar",
|
||||
"remove_main_permission_not_allowed": "No es pot eliminar el permís principal",
|
||||
"remove_user_of_group_not_allowed": "No es pot eliminar l'usuari {user:s} del grup {group:s}",
|
||||
"system_groupname_exists": "El nom de grup ja existeix en el sistema de grups",
|
||||
"tools_update_failed_to_app_fetchlist": "No s'ha pogut actualitzar la llista d'aplicacions de YunoHost a causa de: {error}",
|
||||
"user_already_in_group": "L'usuari {user:s} ja és en el grup {group:s}",
|
||||
"user_not_in_group": "L'usuari {user:s} no és en el grup {group:s}",
|
||||
"migration_description_0012_postgresql_password_to_md5_authentication": "Força l'autenticació PostgreSQL a fer servir MD5 per a les connexions locals",
|
||||
"app_full_domain_unavailable": "Aquesta aplicació ha de ser instal·lada en el seu propi domini, però ja hi ha altres aplicacions instal·lades en el domini «{domain}». Podeu utilitzar un subdomini dedicat a aquesta aplicació.",
|
||||
"migrations_not_pending_cant_skip": "Aquestes migracions no estan pendents, així que no poden ser omeses: {ids}",
|
||||
"app_action_broke_system": "Aquesta acció sembla haver trencat els següents serveis importants: {services}",
|
||||
"log_permission_urls": "Actualitzar les URLs relacionades amb el permís «{}»",
|
||||
"log_user_group_create": "Crear grup «{}»",
|
||||
"log_user_permission_update": "Actualitzar els accessos per al permís «{}»",
|
||||
"log_user_permission_reset": "Restablir el permís «{}»",
|
||||
"permission_already_disallowed": "El grup «{group}» ja té el permís «{permission}» desactivat",
|
||||
"migrations_already_ran": "Aquestes migracions ja s'han fet: {ids}",
|
||||
"migrations_dependencies_not_satisfied": "Executeu aquestes migracions: «{dependencies_id}», abans la migració {id}.",
|
||||
"migrations_failed_to_load_migration": "No s'ha pogut carregar la migració {id}: {error}",
|
||||
"migrations_exclusive_options": "«--auto», «--skip», i «--force-rerun» són opcions mútuament excloents.",
|
||||
"migrations_must_provide_explicit_targets": "Heu de proporcionar objectius explícits al utilitzar «--skip» o «--force-rerun»",
|
||||
"migrations_no_such_migration": "No hi ha cap migració anomenada «{id}»",
|
||||
"migrations_pending_cant_rerun": "Aquestes migracions encara estan pendents, així que no es poden tornar a executar: {ids}",
|
||||
"migrations_running_forward": "Executant la migració {id}…",
|
||||
"migrations_success_forward": "Migració {id} completada",
|
||||
"apps_already_up_to_date": "Ja estan actualitzades totes les aplicacions",
|
||||
"dyndns_provider_unreachable": "No s'ha pogut connectar amb el proveïdor Dyndns {provider}: o el vostre YunoHost no està ben connectat a Internet o el servidor dynette està caigut.",
|
||||
"operation_interrupted": "S'ha interromput manualment l'operació?",
|
||||
"group_already_exist": "El grup {group} ja existeix",
|
||||
"group_already_exist_on_system": "El grup {group} ja existeix en els grups del sistema",
|
||||
"group_cannot_be_edited": "El grup {group} no es pot editar manualment.",
|
||||
"group_cannot_be_deleted": "El grup {group} no es pot eliminar manualment.",
|
||||
"group_user_already_in_group": "L'usuari {user} ja està en el grup {group}",
|
||||
"group_user_not_in_group": "L'usuari {user} no està en el grup {group}",
|
||||
"log_permission_create": "Crear el permís «{}»",
|
||||
"log_permission_delete": "Eliminar el permís «{}»",
|
||||
"migration_0011_failed_to_remove_stale_object": "No s'ha pogut eliminar l'objecte obsolet {dn}: {error}",
|
||||
"permission_already_allowed": "El grup «{group}» ja té el permís «{permission}» activat",
|
||||
"permission_cannot_remove_main": "No es permet eliminar un permís principal",
|
||||
"user_already_exists": "L'usuari «{user}» ja existeix",
|
||||
"app_upgrade_stopped": "S'ha aturat l'actualització de totes les aplicacions per prevenir possibles danys ja que no s'ha pogut actualitzar una aplicació",
|
||||
"app_install_failed": "No s'ha pogut instal·lar {app}: {error}",
|
||||
"app_install_script_failed": "Hi ha hagut un error en el script d'instal·lació de l'aplicació",
|
||||
"group_cannot_edit_all_users": "El grup «all_users» no es pot editar manualment. És un grup especial destinat a contenir els usuaris registrats a YunoHost",
|
||||
"group_cannot_edit_visitors": "El grup «visitors» no es pot editar manualment. És un grup especial que representa els visitants anònims",
|
||||
"group_cannot_edit_primary_group": "El grup «{group}» no es pot editar manualment. És el grup principal destinat a contenir un usuari específic.",
|
||||
"log_permission_url": "Actualització de la URL associada al permís «{}»",
|
||||
"migration_0011_slapd_config_will_be_overwritten": "Sembla que heu modificat manualment la configuració de sldap. Per aquesta migració crítica, YunoHost ha de forçar l'actualització de la configuració sldap. Es farà una còpia de seguretat a {conf_backup_folder}.",
|
||||
"permission_already_up_to_date": "No s'ha actualitzat el permís perquè la petició d'afegir/eliminar ja corresponent a l'estat actual.",
|
||||
"permission_currently_allowed_for_visitors": "El permís ja el tenen el grup de visitants a més d'altres grups. Segurament s'hauria de revocar el permís al grup dels visitants o eliminar els altres grups als que s'ha atribuït.",
|
||||
"permission_currently_allowed_for_all_users": "El permís ha el té el grup de tots els usuaris (all_users) a més d'altres grups. Segurament s'hauria de revocar el permís a «all_users» o eliminar els altres grups als que s'ha atribuït.",
|
||||
"permission_require_account": "El permís {permission} només té sentit per als usuaris que tenen un compte, i per tant no es pot activar per als visitants.",
|
||||
"app_remove_after_failed_install": "Eliminant l'aplicació després que hagi fallat la instal·lació…"
|
||||
}
|
||||
|
|
216
locales/de.json
216
locales/de.json
|
@ -2,27 +2,27 @@
|
|||
"action_invalid": "Ungültige Aktion '{action:s}'",
|
||||
"admin_password": "Administrator-Passwort",
|
||||
"admin_password_change_failed": "Passwort kann nicht geändert werden",
|
||||
"admin_password_changed": "Das Administrator-Kennwort wurde erfolgreich geändert",
|
||||
"admin_password_changed": "Das Administrator-Kennwort wurde geändert",
|
||||
"app_already_installed": "{app:s} ist schon installiert",
|
||||
"app_argument_choice_invalid": "Ungültige Auswahl für Argument '{name:s}'. Es muss einer der folgenden Werte sein {choices:s}",
|
||||
"app_argument_invalid": "Das Argument '{name:s}' hat einen falschen Wert: {error:s}",
|
||||
"app_argument_choice_invalid": "Wähle einen der folgenden Werte '{choices:s}' für das Argument '{name:s}'",
|
||||
"app_argument_invalid": "Wähle einen gültigen Wert für das Argument '{name: s}': {error: s}",
|
||||
"app_argument_required": "Argument '{name:s}' wird benötigt",
|
||||
"app_extraction_failed": "Installationsdateien konnten nicht entpackt werden",
|
||||
"app_id_invalid": "Falsche App-ID",
|
||||
"app_install_files_invalid": "Ungültige Installationsdateien",
|
||||
"app_location_already_used": "Eine andere App ({app}) ist bereits an diesem Ort ({path}) installiert",
|
||||
"app_location_install_failed": "Die App kann nicht an diesem Ort installiert werden, da es mit der App {other_app} die bereits in diesem Pfad ({other_path}) installiert ist Probleme geben würde",
|
||||
"app_manifest_invalid": "Ungültiges App-Manifest: {error}",
|
||||
"app_no_upgrade": "Keine Aktualisierungen für Apps verfügbar",
|
||||
"app_not_installed": "{app:s} ist nicht installiert",
|
||||
"app_install_files_invalid": "Diese Dateien können nicht installiert werden",
|
||||
"app_location_already_used": "Die App ({app}) ist bereits hier ({path}) installiert",
|
||||
"app_location_install_failed": "Die App kann dort nicht installiert werden, da ein Konflikt mit der App '{other_app}' besteht, die bereits in '{other_path}' installiert ist",
|
||||
"app_manifest_invalid": "Mit dem App-Manifest stimmt etwas nicht: {error}",
|
||||
"app_no_upgrade": "Alle Apps sind bereits aktuell",
|
||||
"app_not_installed": "Die App {app:s} konnte nicht in der Liste installierter Apps gefunden werden: {all_apps}",
|
||||
"app_recent_version_required": "Für {:s} benötigt eine aktuellere Version von moulinette",
|
||||
"app_removed": "{app:s} wurde erfolgreich entfernt",
|
||||
"app_sources_fetch_failed": "Quelldateien konnten nicht abgerufen werden",
|
||||
"app_removed": "{app:s} wurde entfernt",
|
||||
"app_sources_fetch_failed": "Quelldateien konnten nicht abgerufen werden, ist die URL korrekt?",
|
||||
"app_unknown": "Unbekannte App",
|
||||
"app_upgrade_failed": "{app:s} konnte nicht aktualisiert werden",
|
||||
"app_upgraded": "{app:s} wurde erfolgreich aktualisiert",
|
||||
"appslist_fetched": "Appliste {appslist:s} wurde erfolgreich heruntergelanden",
|
||||
"appslist_removed": "Appliste {appslist:s} wurde erfolgreich entfernt",
|
||||
"app_upgraded": "{app:s} aktualisiert",
|
||||
"appslist_fetched": "Appliste {appslist:s} wurde erfolgreich gelanden",
|
||||
"appslist_removed": "Appliste {appslist:s} wurde entfernt",
|
||||
"appslist_retrieve_error": "Entfernte Appliste {appslist:s} kann nicht empfangen werden: {error:s}",
|
||||
"appslist_unknown": "Appliste {appslist:s} ist unbekannt.",
|
||||
"ask_current_admin_password": "Derzeitiges Administrator-Kennwort",
|
||||
|
@ -34,26 +34,26 @@
|
|||
"ask_new_admin_password": "Neues Verwaltungskennwort",
|
||||
"ask_password": "Passwort",
|
||||
"backup_action_required": "Du musst etwas zum Speichern auswählen",
|
||||
"backup_app_failed": "Konnte keine Sicherung für '{app:s}' erstellen",
|
||||
"backup_app_failed": "Konnte keine Sicherung für die App '{app:s}' erstellen",
|
||||
"backup_archive_app_not_found": "App '{app:s}' konnte in keiner Datensicherung gefunden werden",
|
||||
"backup_archive_hook_not_exec": "Hook '{hook:s}' konnte für diese Datensicherung nicht ausgeführt werden",
|
||||
"backup_archive_name_exists": "Datensicherung mit dem selben Namen existiert bereits",
|
||||
"backup_archive_name_exists": "Datensicherung mit dem selben Namen existiert bereits.",
|
||||
"backup_archive_name_unknown": "Unbekanntes lokale Datensicherung mit Namen '{name:s}' gefunden",
|
||||
"backup_archive_open_failed": "Kann Sicherungsarchiv nicht öfnen",
|
||||
"backup_cleaning_failed": "Temporäres Sicherungsverzeichnis konnte nicht geleert werden",
|
||||
"backup_created": "Datensicherung komplett",
|
||||
"backup_creating_archive": "Datensicherung wird erstellt...",
|
||||
"backup_creating_archive": "Datensicherung wird erstellt…",
|
||||
"backup_delete_error": "Pfad '{path:s}' konnte nicht gelöscht werden",
|
||||
"backup_deleted": "Datensicherung wurde entfernt",
|
||||
"backup_deleted": "Backup wurde entfernt",
|
||||
"backup_extracting_archive": "Entpacke Sicherungsarchiv...",
|
||||
"backup_hook_unknown": "Datensicherungshook '{hook:s}' unbekannt",
|
||||
"backup_invalid_archive": "Ungültige Datensicherung",
|
||||
"backup_nothings_done": "Es gibt keine Änderungen zur Speicherung",
|
||||
"backup_output_directory_forbidden": "Verbotenes Ausgabeverzeichnis. Datensicherung können nicht in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var oder in Unterordnern von /home/yunohost.backup/archives erstellt werden",
|
||||
"backup_output_directory_not_empty": "Ausgabeordner ist nicht leer",
|
||||
"backup_hook_unknown": "Der Datensicherungshook '{hook:s}' unbekannt",
|
||||
"backup_invalid_archive": "Dies ist kein Backup-Archiv",
|
||||
"backup_nothings_done": "Keine Änderungen zur Speicherung",
|
||||
"backup_output_directory_forbidden": "Wähle ein anderes Ausgabeverzeichnis. Datensicherung können nicht in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var oder in Unterordnern von /home/yunohost.backup/archives erstellt werden",
|
||||
"backup_output_directory_not_empty": "Der gewählte Ausgabeordner sollte leer sein",
|
||||
"backup_output_directory_required": "Für die Datensicherung muss ein Zielverzeichnis angegeben werden",
|
||||
"backup_running_app_script": "Datensicherung für App '{app:s}' wurd durchgeführt...",
|
||||
"backup_running_hooks": "Datensicherunghook wird ausgeführt...",
|
||||
"backup_running_hooks": "Datensicherunghook wird ausgeführt…",
|
||||
"custom_app_url_required": "Es muss eine URL angegeben werden, um deine benutzerdefinierte App {app:s} zu aktualisieren",
|
||||
"custom_appslist_name_required": "Du musst einen Namen für deine benutzerdefinierte Appliste angeben",
|
||||
"dnsmasq_isnt_installed": "dnsmasq scheint nicht installiert zu sein. Bitte führe 'apt-get remove bind9 && apt-get install dnsmasq' aus",
|
||||
|
@ -71,7 +71,7 @@
|
|||
"domain_zone_exists": "DNS Zonen Datei existiert bereits",
|
||||
"domain_zone_not_found": "DNS Zonen Datei kann nicht für Domäne {:s} gefunden werden",
|
||||
"done": "Erledigt",
|
||||
"downloading": "Wird heruntergeladen...",
|
||||
"downloading": "Wird heruntergeladen…",
|
||||
"dyndns_cron_installed": "DynDNS Cronjob erfolgreich angelegt",
|
||||
"dyndns_cron_remove_failed": "Der DynDNS Cronjob konnte nicht entfernt werden",
|
||||
"dyndns_cron_removed": "Der DynDNS Cronjob wurde gelöscht",
|
||||
|
@ -81,9 +81,9 @@
|
|||
"dyndns_registered": "Deine DynDNS Domain wurde registriert",
|
||||
"dyndns_registration_failed": "DynDNS Domain konnte nicht registriert werden: {error:s}",
|
||||
"dyndns_unavailable": "DynDNS Subdomain ist nicht verfügbar",
|
||||
"executing_command": "Führe den Behfehl '{command:s}' aus...",
|
||||
"executing_script": "Skript '{script:s}' wird ausgeührt...",
|
||||
"extracting": "Wird entpackt...",
|
||||
"executing_command": "Führe den Behfehl '{command:s}' aus…",
|
||||
"executing_script": "Skript '{script:s}' wird ausgeührt…",
|
||||
"extracting": "Wird entpackt…",
|
||||
"field_invalid": "Feld '{:s}' ist unbekannt",
|
||||
"firewall_reload_failed": "Die Firewall konnte nicht neu geladen werden",
|
||||
"firewall_reloaded": "Die Firewall wurde neu geladen",
|
||||
|
@ -104,8 +104,8 @@
|
|||
"mail_alias_remove_failed": "E-Mail Alias '{mail:s}' konnte nicht entfernt werden",
|
||||
"mail_domain_unknown": "Unbekannte Mail Domain '{domain:s}'",
|
||||
"mail_forward_remove_failed": "Mailweiterleitung '{mail:s}' konnte nicht entfernt werden",
|
||||
"maindomain_change_failed": "Die Hauptdomain konnte nicht geändert werden",
|
||||
"maindomain_changed": "Die Hauptdomain wurde geändert",
|
||||
"main_domain_change_failed": "Die Hauptdomain konnte nicht geändert werden",
|
||||
"main_domain_changed": "Die Hauptdomain wurde geändert",
|
||||
"monitor_disabled": "Das Servermonitoring wurde erfolgreich deaktiviert",
|
||||
"monitor_enabled": "Das Servermonitoring wurde aktiviert",
|
||||
"monitor_glances_con_failed": "Verbindung mit Glances nicht möglich",
|
||||
|
@ -156,7 +156,7 @@
|
|||
"restore_hook_unavailable": "Das Wiederherstellungsskript für '{part:s}' steht weder in deinem System noch im Archiv zur Verfügung",
|
||||
"restore_nothings_done": "Es wurde nicht wiederhergestellt",
|
||||
"restore_running_app_script": "Wiederherstellung wird ausfeührt für App '{app:s}'...",
|
||||
"restore_running_hooks": "Wiederherstellung wird gestartet...",
|
||||
"restore_running_hooks": "Wiederherstellung wird gestartet…",
|
||||
"service_add_configuration": "Füge Konfigurationsdatei {file:s} hinzu",
|
||||
"service_add_failed": "Der Dienst '{service:s}' kann nicht hinzugefügt werden",
|
||||
"service_added": "Der Service '{service:s}' wurde erfolgreich hinzugefügt",
|
||||
|
@ -189,9 +189,9 @@
|
|||
"unlimit": "Kein Kontingent",
|
||||
"unrestore_app": "App '{app:s}' kann nicht Wiederhergestellt werden",
|
||||
"update_cache_failed": "Konnte APT cache nicht aktualisieren",
|
||||
"updating_apt_cache": "Die Liste der verfügbaren Pakete wird aktualisiert...",
|
||||
"updating_apt_cache": "Die Liste der verfügbaren Pakete wird aktualisiert…",
|
||||
"upgrade_complete": "Upgrade vollständig",
|
||||
"upgrading_packages": "Pakete werden aktualisiert...",
|
||||
"upgrading_packages": "Pakete werden aktualisiert…",
|
||||
"upnp_dev_not_found": "Es konnten keine UPnP Geräte gefunden werden",
|
||||
"upnp_disabled": "UPnP wurde deaktiviert",
|
||||
"upnp_enabled": "UPnP wurde aktiviert",
|
||||
|
@ -208,12 +208,12 @@
|
|||
"yunohost_already_installed": "YunoHost ist bereits installiert",
|
||||
"yunohost_ca_creation_failed": "Zertifikatsstelle konnte nicht erstellt werden",
|
||||
"yunohost_configured": "YunoHost wurde konfiguriert",
|
||||
"yunohost_installing": "YunoHost wird installiert...",
|
||||
"yunohost_installing": "YunoHost wird installiert…",
|
||||
"yunohost_not_installed": "YunoHost ist nicht oder unvollständig installiert worden. Bitte 'yunohost tools postinstall' ausführen",
|
||||
"app_not_properly_removed": "{app:s} wurde nicht ordnungsgemäß entfernt",
|
||||
"service_regenconf_failed": "Konnte die Konfiguration für folgende Dienste nicht neu erzeugen: {services}",
|
||||
"not_enough_disk_space": "Zu wenig freier Speicherplatz unter '{path:s}' verfügbar",
|
||||
"backup_creation_failed": "Erstellen des Backups fehlgeschlagen",
|
||||
"backup_creation_failed": "Konnte Backup-Archiv nicht erstellen",
|
||||
"service_conf_up_to_date": "Die Konfiguration für den Dienst '{service}' ist bereits aktuell",
|
||||
"package_not_installed": "Das Paket '{pkgname}' ist nicht installiert",
|
||||
"pattern_positive_number": "Muss eine positive Zahl sein",
|
||||
|
@ -221,7 +221,7 @@
|
|||
"package_unexpected_error": "Ein unerwarteter Fehler trat bei der Verarbeitung des Pakets '{pkgname}' auf",
|
||||
"app_incompatible": "Die Anwendung {app} ist nicht mit deiner YunoHost-Version kompatibel",
|
||||
"app_not_correctly_installed": "{app:s} scheint nicht korrekt installiert zu sein",
|
||||
"app_requirements_checking": "Überprüfe notwendige Pakete für {app}...",
|
||||
"app_requirements_checking": "Überprüfe notwendige Pakete für {app}…",
|
||||
"app_requirements_failed": "Anforderungen für {app} werden nicht erfüllt: {error}",
|
||||
"app_requirements_unmeet": "Anforderungen für {app} werden nicht erfüllt, das Paket {pkgname} ({version}) muss {spec} sein",
|
||||
"app_unsupported_remote_type": "Für die App wurde ein nicht unterstützer Steuerungstyp verwendet",
|
||||
|
@ -277,29 +277,145 @@
|
|||
"service_regenconf_pending_applying": "Überprüfe ausstehende Konfigurationen, die für den Server '{service}' notwendig sind...",
|
||||
"certmanager_http_check_timeout": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte sich selbst über HTTP mit der öffentlichen IP (Domain {domain:s} mit der IP {ip:s}) zu erreichen. Möglicherweise ist dafür hairpinning oder eine falsch konfigurierte Firewall/Router deines Servers dafür verantwortlich.",
|
||||
"certmanager_couldnt_fetch_intermediate_cert": "Eine Zeitüberschreitung ist aufgetreten als der Server versuchte die Teilzertifikate von Let's Encrypt zusammenzusetzen. Die Installation/Erneuerung des Zertifikats wurde abgebrochen - bitte versuche es später erneut.",
|
||||
"appslist_retrieve_bad_format": "Die empfangene Datei der Appliste {appslist:s} ist ungültig",
|
||||
"appslist_retrieve_bad_format": "Die geladene Appliste {appslist:s} ist ungültig",
|
||||
"domain_hostname_failed": "Erstellen des neuen Hostnamens fehlgeschlagen",
|
||||
"appslist_name_already_tracked": "Es gibt bereits eine registrierte App-Liste mit Namen {name:s}.",
|
||||
"appslist_url_already_tracked": "Es gibt bereits eine registrierte Anwendungsliste mit dem URL {url:s}.",
|
||||
"appslist_migrating": "Migriere Anwendungsliste {appslist:s} ...",
|
||||
"appslist_could_not_migrate": "Konnte Anwendungsliste {appslist:s} nicht migrieren. Konnte die URL nicht verarbeiten... Der alte Cron-Job wurde unter {bkp_file:s} beibehalten.",
|
||||
"appslist_corrupted_json": "Konnte die Anwendungslisten. Es scheint, dass {filename:s} beschädigt ist.",
|
||||
"appslist_url_already_tracked": "Es gibt bereits eine registrierte Anwendungsliste mit der URL {url:s}.",
|
||||
"appslist_migrating": "Migriere Anwendungsliste {appslist:s} …",
|
||||
"appslist_could_not_migrate": "Konnte die Anwendungsliste {appslist:s} nicht migrieren. Konnte die URL nicht verarbeiten... Der alte Cron-Job wurde unter {bkp_file:s} beibehalten.",
|
||||
"appslist_corrupted_json": "Anwendungslisten konnte nicht geladen werden. Es scheint, dass {filename:s} beschädigt ist.",
|
||||
"yunohost_ca_creation_success": "Die lokale Zertifizierungs-Authorität wurde angelegt.",
|
||||
"app_already_installed_cant_change_url": "Diese Application ist bereits installiert. Die URL kann durch diese Funktion nicht modifiziert werden. Überprüfe ob `app changeurl` verfügbar ist.",
|
||||
"app_change_no_change_url_script": "Die Application {app_name:s} unterstützt das anpassen der URL noch nicht. Sie muss gegebenenfalls erweitert werden.",
|
||||
"app_change_url_failed_nginx_reload": "NGINX konnte nicht neu gestartet werden. Hier ist der Output von 'nginx -t':\n{nginx_errors:s}",
|
||||
"app_change_url_identical_domains": "Die alte und neue domain/url_path sind identisch: ('{domain:s} {path:s}'). Es gibt nichts zu tun.",
|
||||
"app_already_up_to_date": "{app:s} ist schon aktuell",
|
||||
"app_already_up_to_date": "{app:s} ist bereits aktuell",
|
||||
"backup_abstract_method": "Diese Backup-Methode wird noch nicht unterstützt",
|
||||
"backup_applying_method_tar": "Erstellen des Backup-tar Archives...",
|
||||
"backup_applying_method_copy": "Kopiere alle Dateien ins Backup...",
|
||||
"app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modufikation. Vielleicht gibt es eine Aktualisierung der Anwendung.",
|
||||
"backup_applying_method_tar": "Erstellen des Backup-tar Archives…",
|
||||
"backup_applying_method_copy": "Kopiere alle Dateien ins Backup…",
|
||||
"app_change_url_no_script": "Die Anwendung '{app_name:s}' unterstützt bisher keine URL-Modifikation. Vielleicht sollte sie aktualisiert werden.",
|
||||
"app_location_unavailable": "Diese URL ist nicht verfügbar oder wird von einer installierten Anwendung genutzt:\n{apps:s}",
|
||||
"backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf...",
|
||||
"backup_applying_method_custom": "Rufe die benutzerdefinierte Backup-Methode '{method:s}' auf…",
|
||||
"backup_archive_system_part_not_available": "Der System-Teil '{part:s}' ist in diesem Backup nicht enthalten",
|
||||
"backup_archive_mount_failed": "Das Einbinden des Backup-Archives ist fehlgeschlagen",
|
||||
"backup_archive_writing_error": "Die Dateien konnten nicht in der komprimierte Archiv-Backup hinzugefügt werden",
|
||||
"app_change_url_success": "Erfolgreiche Änderung der URL von {app:s} zu {domain:s}{path:s}",
|
||||
"backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository...",
|
||||
"invalid_url_format": "ungültiges URL Format"
|
||||
"backup_archive_writing_error": "Die Dateien '{source:s} (im Ordner '{dest:s}') konnten nicht in das komprimierte Archiv-Backup '{archive:s}' hinzugefügt werden",
|
||||
"app_change_url_success": "{app:s} URL ist nun {domain:s}{path:s}",
|
||||
"backup_applying_method_borg": "Sende alle Dateien zur Sicherung ins borg-backup repository…",
|
||||
"invalid_url_format": "ungültiges URL Format",
|
||||
"global_settings_bad_type_for_setting": "Falscher Typ für Einstellung {setting: s}. Empfangen: {receive_type: s}, aber erwartet: {expected_type: s}",
|
||||
"global_settings_bad_choice_for_enum": "Falsche Wahl für die Einstellung {setting: s}. Habe '{choice: s}' erhalten, aber es stehen nur folgende Auswahlmöglichkeiten zur Verfügung: {available_choices: s}",
|
||||
"file_does_not_exist": "Die Datei {path: s} existiert nicht.",
|
||||
"experimental_feature": "Warnung: Diese Funktion ist experimentell und gilt nicht als stabil. Sie sollten sie nur verwenden, wenn Sie wissen, was Sie tun.",
|
||||
"error_when_removing_sftpuser_group": "Fehler beim Versuch, die Gruppe sftpusers zu entfernen",
|
||||
"edit_permission_with_group_all_users_not_allowed": "Sie dürfen die Berechtigung für die Gruppe \"all_users\" nicht bearbeiten. Verwenden Sie stattdessen \"yunohost user permission clear APP\" oder \"yunohost user permission add APP -u USER\".",
|
||||
"edit_group_not_allowed": "Du bist nicht berechtigt zum Bearbeiten der Gruppe {group: s}",
|
||||
"dyndns_domain_not_provided": "Der Dyndns-Anbieter {provider: s} kann die Domain(s) {domain: s} nicht bereitstellen.",
|
||||
"dyndns_could_not_check_available": "Konnte nicht überprüfen, ob {domain: s} auf {provider: s} verfügbar ist.",
|
||||
"dyndns_could_not_check_provide": "Konnte nicht überprüft, ob {provider: s} die Domain(s) {domain: s} bereitstellen kann.",
|
||||
"domain_dyndns_dynette_is_unreachable": "YunoHost dynette kann nicht erreicht werden, entweder ist Ihr YunoHost nicht korrekt mit dem Internet verbunden oder der dynette-Server ist inaktiv. Fehler: {error}",
|
||||
"domain_dns_conf_is_just_a_recommendation": "Dieser Befehl zeigt Ihnen, was die * empfohlene * Konfiguration ist. Die DNS-Konfiguration wird NICHT für Sie eingerichtet. Es liegt in Ihrer Verantwortung, Ihre DNS-Zone in Ihrem Registrar gemäß dieser Empfehlung zu konfigurieren.",
|
||||
"dpkg_lock_not_available": "Dieser Befehl kann momentan nicht ausgeführt werden, da anscheinend ein anderes Programm die Sperre von dpkg (dem Systempaket-Manager) verwendet",
|
||||
"confirm_app_install_thirdparty": "WARNUNG! Das Installieren von Anwendungen von Drittanbietern kann die Integrität und Sicherheit Deines Systems beeinträchtigen. Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers: s}] ",
|
||||
"confirm_app_install_danger": "WARNUNG! Diese Anwendung ist noch experimentell (wenn nicht ausdrücklich \"not working\"/\"funktioniert nicht\") und es ist wahrscheinlich, dass Dein System Schaden nimmt! Du solltest es wahrscheinlich NICHT installieren, es sei denn, Du weisst, was Du tust. Bist du bereit, dieses Risiko einzugehen? [{answers: s}] ",
|
||||
"confirm_app_install_warning": "Warnung: Diese Anwendung funktioniert möglicherweise, ist jedoch nicht gut in YunoHost integriert. Einige Funktionen wie Single Sign-On und Backup / Restore sind möglicherweise nicht verfügbar. Trotzdem installieren? [{answers: s}] ",
|
||||
"backup_with_no_restore_script_for_app": "App {app: s} hat kein Wiederherstellungsskript. Das Backup dieser App kann nicht automatisch wiederhergestellt werden.",
|
||||
"backup_with_no_backup_script_for_app": "App {app: s} hat kein Sicherungsskript. Ignoriere es.",
|
||||
"backup_unable_to_organize_files": "Dateien im Archiv können mit der schnellen Methode nicht organisiert werden",
|
||||
"backup_system_part_failed": "Der Systemteil '{part: s}' kann nicht gesichert werden",
|
||||
"backup_permission": "Sicherungsberechtigung für App {app: s}",
|
||||
"backup_output_symlink_dir_broken": "Sie haben einen fehlerhaften Symlink anstelle Ihres Archivverzeichnisses '{path: s}'. Möglicherweise haben Sie ein spezielles Setup, um Ihre Daten auf einem anderen Dateisystem zu sichern. In diesem Fall haben Sie wahrscheinlich vergessen, Ihre Festplatte oder Ihren USB-Schlüssel erneut einzuhängen oder anzuschließen.",
|
||||
"backup_mount_archive_for_restore": "Archiv für Wiederherstellung vorbereiten…",
|
||||
"backup_method_tar_finished": "Tar-Backup-Archiv erstellt",
|
||||
"backup_method_custom_finished": "Benutzerdefinierte Sicherungsmethode '{method: s}' beendet",
|
||||
"backup_method_copy_finished": "Sicherungskopie beendet",
|
||||
"backup_method_borg_finished": "Backup in Borg beendet",
|
||||
"backup_custom_need_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Braucht ein Einhängen/Verbinden\" (need_mount) ein Fehler aufgetreten",
|
||||
"backup_custom_mount_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Einhängen/Verbinden\" ein Fehler aufgetreten",
|
||||
"backup_custom_backup_error": "Bei der benutzerdefinierten Sicherungsmethode ist beim Arbeitsschritt \"Sicherung\" ein Fehler aufgetreten",
|
||||
"backup_csv_creation_failed": "Die zur Wiederherstellung erforderliche CSV-Datei kann nicht erstellt werden",
|
||||
"backup_couldnt_bind": "{Src: s} konnte nicht an {dest: s} angebunden werden.",
|
||||
"backup_borg_not_implemented": "Die Borg-Sicherungsmethode ist noch nicht implementiert",
|
||||
"backup_ask_for_copying_if_needed": "Einige Dateien konnten mit der Methode, die es vermeidet vorübergehend Speicherplatz auf dem System zu verschwenden, nicht gesichert werden. Zur Durchführung der Sicherung sollten vorübergehend {size: s} MB verwendet werden. Sind Sie einverstanden?",
|
||||
"backup_actually_backuping": "Erstellt ein Backup-Archiv aus den gesammelten Dateien …",
|
||||
"ask_path": "Pfad",
|
||||
"ask_new_path": "Neuer Pfad",
|
||||
"ask_new_domain": "Neue Domain",
|
||||
"apps_permission_restoration_failed": "Erteilen der Berechtigung '{permission: s}' für die Wiederherstellung der App {app: s} erforderlich",
|
||||
"apps_permission_not_found": "Keine Berechtigung für die installierten Apps gefunden",
|
||||
"app_upgrade_some_app_failed": "Einige Anwendungen können nicht aktualisiert werden",
|
||||
"app_upgrade_app_name": "{App} wird jetzt aktualisiert…",
|
||||
"app_upgrade_several_apps": "Die folgenden Apps werden aktualisiert: {apps}",
|
||||
"app_start_restore": "Anwendung {app} wird wiederhergestellt…",
|
||||
"app_start_backup": "Sammeln von Dateien, die für {app} gesichert werden sollen…",
|
||||
"app_start_remove": "Anwendung {app} wird entfernt…",
|
||||
"app_start_install": "Anwendung {app} wird installiert…",
|
||||
"app_not_upgraded": "Die App '{failed_app}' konnte nicht aktualisiert werden. Infolgedessen wurden die folgenden App-Upgrades abgebrochen: {apps}",
|
||||
"app_make_default_location_already_used": "Die App \"{app}\" kann nicht als Standard für die Domain \"{domain}\" festgelegt werden. Sie wird bereits von der App \"{other_app}\" verwendet",
|
||||
"aborting": "Breche ab.",
|
||||
"app_action_cannot_be_ran_because_required_services_down": "Diese App erfordert einige Dienste, die derzeit nicht verfügbar sind. Bevor Sie fortfahren, sollten Sie versuchen, die folgenden Dienste neu zu starten (und möglicherweise untersuchen, warum sie nicht verfügbar sind): {services}",
|
||||
"already_up_to_date": "Nichts zu tun. Alles ist bereits auf dem neusten Stand.",
|
||||
"admin_password_too_long": "Bitte ein Passwort kürzer als 127 Zeichen wählen",
|
||||
"app_action_broke_system": "Diese Aktion hat anscheinend diese wichtigen Services gestört: {services}",
|
||||
"apps_already_up_to_date": "Alle Apps sind bereits aktuell",
|
||||
"backup_copying_to_organize_the_archive": "Kopieren von {size: s} MB, um das Archiv zu organisieren",
|
||||
"app_upgrade_stopped": "Das Upgrade aller Anwendungen wurde gestoppt, um mögliche Schäden zu vermeiden, da das Upgrade der vorherigen Anwendung fehlgeschlagen ist",
|
||||
"group_already_disallowed": "Die Gruppe '{group:s}' hat bereits die Berechtigungen '{permission:s}' für die App '{app:s}' deaktiviert",
|
||||
"global_settings_setting_security_ssh_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den SSH-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)",
|
||||
"group_deleted": "Gruppe '{group}' gelöscht",
|
||||
"group_deletion_failed": "Kann Gruppe '{group}' nicht löschen",
|
||||
"group_deletion_not_allowed": "Die Gruppe {group:s} kann nicht manuell gelöscht werden.",
|
||||
"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.",
|
||||
"group_already_allowed": "Gruppe '{group:s}' hat bereits die Berechtigung '{permission:s}' für die App '{app:s}' eingeschaltet",
|
||||
"group_name_already_exist": "Gruppe {name:s} existiert bereits",
|
||||
"group_created": "Gruppe '{group}' angelegt",
|
||||
"group_creation_failed": "Kann Gruppe '{group}' nicht anlegen",
|
||||
"group_unknown": "Die Gruppe '{group:s}' ist unbekannt",
|
||||
"group_updated": "Gruppe '{group:s}' erneuert",
|
||||
"group_update_failed": "Kann Gruppe '{group:s}' nicht anpassen",
|
||||
"log_does_exists": "Es gibt kein Operationsprotokoll mit dem Namen'{log}', verwende'yunohost log list', um alle verfügbaren Operationsprotokolle anzuzeigen",
|
||||
"log_app_removelist": "Entferne eine Applikationsliste",
|
||||
"log_operation_unit_unclosed_properly": "Die Operationseinheit wurde nicht richtig geschlossen",
|
||||
"log_app_removeaccess": "Entziehe Zugriff auf '{}'",
|
||||
"global_settings_setting_security_postfix_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den Postfix-Server. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)",
|
||||
"log_category_404": "Die Log-Kategorie '{category}' existiert nicht",
|
||||
"global_settings_unknown_type": "Unerwartete Situation, die Einstellung {setting:s} scheint den Typ {unknown_type:s} 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 dpkg --configure -a` ausführst.",
|
||||
"global_settings_unknown_setting_from_settings_file": "Unbekannter Schlüssel in den Einstellungen: '{setting_key:s}', 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>'",
|
||||
"global_settings_setting_example_bool": "Beispiel einer booleschen Option",
|
||||
"log_app_fetchlist": "Füge eine Applikationsliste hinzu",
|
||||
"log_help_to_get_log": "Um das Protokoll der Operation '{desc}' anzuzeigen, verwende den Befehl 'yunohost log display {name}'",
|
||||
"global_settings_setting_security_nginx_compatibility": "Kompatibilität vs. Sicherheitskompromiss für den Webserver NGINX. Beeinflusst die Chiffren (und andere sicherheitsrelevante Aspekte)",
|
||||
"backup_php5_to_php7_migration_may_fail": "Dein Archiv konnte nicht für PHP 7 konvertiert werden, Du kannst deine PHP-Anwendungen möglicherweise nicht wiederherstellen (Grund: {error:s})",
|
||||
"global_settings_setting_service_ssh_allow_deprecated_dsa_hostkey": "Erlaubt die Verwendung eines (veralteten) DSA-Hostkeys für die SSH-Daemon-Konfiguration",
|
||||
"global_settings_setting_example_string": "Beispiel einer string Option",
|
||||
"log_app_addaccess": "Füge Zugriff auf '{}' hinzu",
|
||||
"log_app_remove": "Entferne die Anwendung '{}'",
|
||||
"global_settings_setting_example_int": "Beispiel einer int Option",
|
||||
"global_settings_cant_open_settings": "Einstellungsdatei konnte nicht geöffnet werden, Grund: {reason:s}",
|
||||
"global_settings_cant_write_settings": "Einstellungsdatei konnte nicht gespeichert werden, Grund: {reason:s}",
|
||||
"log_app_install": "Installiere die Anwendung '{}'",
|
||||
"global_settings_reset_success": "Frühere Einstellungen werden nun auf {path:s} gesichert",
|
||||
"log_app_upgrade": "Upgrade der Anwendung '{}'",
|
||||
"good_practices_about_admin_password": "Sie sind nun dabei, ein neues Administrationspasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein - obwohl es sinnvoll ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.",
|
||||
"log_corrupted_md_file": "Die mit Protokollen verknüpfte YAML-Metadatendatei ist beschädigt: '{md_file}\nFehler: {error}''",
|
||||
"global_settings_cant_serialize_settings": "Einstellungsdaten konnten nicht serialisiert werden, Grund: {reason:s}",
|
||||
"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 display {name} --share', um Hilfe zu erhalten",
|
||||
"backup_no_uncompress_archive_dir": "Dieses unkomprimierte Archivverzeichnis gibt es nicht",
|
||||
"log_app_change_url": "Ändere die URL der Anwendung '{}'",
|
||||
"global_settings_setting_security_password_user_strength": "Stärke des Benutzerpassworts",
|
||||
"good_practices_about_user_password": "Du bist nun dabei, ein neues Benutzerpasswort zu definieren. Das Passwort sollte mindestens 8 Zeichen lang sein - obwohl es ratsam ist, ein längeres Passwort (z.B. eine Passphrase) und/oder eine Variation von Zeichen (Groß- und Kleinschreibung, Ziffern und Sonderzeichen) zu verwenden.",
|
||||
"global_settings_setting_example_enum": "Beispiel einer enum Option",
|
||||
"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_csv_addition_failed": "Es konnten keine Dateien zur Sicherung in die CSV-Datei hinzugefügt werden",
|
||||
"log_app_clearaccess": "Entziehe alle Zugriffe auf '{}'",
|
||||
"global_settings_setting_security_password_admin_strength": "Stärke des Admin-Passworts",
|
||||
"global_settings_key_doesnt_exists": "Der Schlüssel'{settings_key:s}' 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-Anwendung",
|
||||
"hook_json_return_error": "Konnte die Rückkehr vom Einsprungpunkt {path:s} nicht lesen. Fehler: {msg:s}. Unformatierter Inhalt: {raw_content}",
|
||||
"app_full_domain_unavailable": "Es tut uns leid, aber diese Anwendung erfordert die Installation auf einer eigenen Domain, aber einige andere Anwendungen sind bereits auf der Domäne'{domain}' installiert. Eine mögliche Lösung ist das Hinzufügen und Verwenden einer Subdomain, die dieser Anwendung zugeordnet ist.",
|
||||
"app_install_failed": "Installation von {app} fehlgeschlagen: {error}",
|
||||
"app_install_script_failed": "Im Installationsscript ist ein Fehler aufgetreten",
|
||||
"app_remove_after_failed_install": "Entfernen der App nach fehlgeschlagener Installation…"
|
||||
}
|
||||
|
|
886
locales/en.json
886
locales/en.json
File diff suppressed because it is too large
Load diff
586
locales/eo.json
586
locales/eo.json
|
@ -1,36 +1,568 @@
|
|||
{
|
||||
"admin_password_change_failed": "Malebla ŝanĝi pasvorton",
|
||||
"admin_password_changed": "Pasvorto de la estro estas ŝanĝita",
|
||||
"admin_password_change_failed": "Ne eblas ŝanĝi pasvorton",
|
||||
"admin_password_changed": "La pasvorto de administrado ŝanĝiĝis",
|
||||
"app_already_installed": "{app:s} estas jam instalita",
|
||||
"app_already_up_to_date": "{app:s} estas ĝisdata",
|
||||
"app_already_up_to_date": "{app:s} estas jam ĝisdata",
|
||||
"app_argument_required": "Parametro {name:s} estas bezonata",
|
||||
"app_change_url_identical_domains": "Malnovaj kaj novaj domajno/URL estas la sama ('{domain:s}{path:s}'), nenio fareblas.",
|
||||
"app_change_url_success": "URL de appo {app:s} ŝanĝita al {domain:s}{path:s}",
|
||||
"app_extraction_failed": "Malebla malkompaktigi instaldosierojn",
|
||||
"app_id_invalid": "Nevalida apo id",
|
||||
"app_change_url_success": "{app:s} URL nun estas {domain:s} {path:s}",
|
||||
"app_extraction_failed": "Ne povis ĉerpi la instalajn dosierojn",
|
||||
"app_id_invalid": "Nevalida apo ID",
|
||||
"app_incompatible": "Apo {app} ne estas kongrua kun via YunoHost versio",
|
||||
"app_install_files_invalid": "Nevalidaj instaldosieroj",
|
||||
"app_location_already_used": "Apo {app} jam estas instalita al tiu loco ({path})",
|
||||
"user_updated": "Uzanto estas ĝisdatita",
|
||||
"app_install_files_invalid": "Ĉi tiuj dosieroj ne povas esti instalitaj",
|
||||
"app_location_already_used": "La app '{app}' jam estas instalita en ({path})",
|
||||
"user_updated": "Uzantinformoj ŝanĝis",
|
||||
"users_available": "Uzantoj disponeblaj :",
|
||||
"yunohost_already_installed": "YunoHost estas jam instalita",
|
||||
"yunohost_ca_creation_failed": "Ne eblas krei atestan aŭtoritaton",
|
||||
"yunohost_ca_creation_success": "Loka atesta aŭtoritato estas kreita.",
|
||||
"yunohost_ca_creation_failed": "Ne povis krei atestan aŭtoritaton",
|
||||
"yunohost_ca_creation_success": "Loka atestila aŭtoritato kreiĝis.",
|
||||
"yunohost_installing": "Instalante YunoHost…",
|
||||
"service_description_glances": "monitoras sisteminformojn de via servilo",
|
||||
"service_description_metronome": "mastrumas XMPP tujmesaĝilon kontojn",
|
||||
"service_description_mysql": "stokas aplikaĵojn datojn (SQL datumbazo)",
|
||||
"service_description_nginx": "servas aŭ permesas atingi ĉiujn retejojn gastigita sur via servilo",
|
||||
"service_description_nslcd": "mastrumas Yunohost uzantojn konektojn per komanda linio",
|
||||
"service_description_php7.0-fpm": "rulas aplikaĵojn skibita en PHP kun nginx",
|
||||
"service_description_postfix": "uzita por sendi kaj ricevi retpoŝtojn",
|
||||
"service_description_redis-server": "specialita datumbazo uzita por rapida datumo atingo, atendovicoj kaj komunikadoj inter programoj",
|
||||
"service_description_rmilter": "kontrolas diversajn parametrojn en retpoŝtoj",
|
||||
"service_description_rspamd": "filtras trudmesaĝojn, kaj aliaj funkcioj rilate al retpoŝto",
|
||||
"service_description_slapd": "stokas uzantojn, domajnojn kaj rilatajn informojn",
|
||||
"service_description_ssh": "permesas al vi konekti al via servilo kun fora terminalo (SSH protokolo)",
|
||||
"service_description_yunohost-api": "mastrumas interagojn inter la YunoHost retinterfaco kaj la sistemo",
|
||||
"service_description_yunohost-firewall": "mastrumas malfermitajn kaj fermitajn konektejojn al servoj",
|
||||
"service_disable_failed": "Neebla malaktivigi servon '{service:s}'\n\nFreŝaj protokoloj de la servo : {logs:s}",
|
||||
"service_disabled": "Servo '{service:s}' estas malaktivigita"
|
||||
"service_description_glances": "Monitoras sistemajn informojn en via servilo",
|
||||
"service_description_metronome": "Mastrumas XMPP tujmesaĝilon kontojn",
|
||||
"service_description_mysql": "Stokas aplikaĵojn datojn (SQL datumbazo)",
|
||||
"service_description_nginx": "Servas aŭ permesas atingi ĉiujn retejojn gastigita sur via servilo",
|
||||
"service_description_nslcd": "Mastrumas Yunohost uzantojn konektojn per komanda linio",
|
||||
"service_description_php7.0-fpm": "Rulas aplikaĵojn skibita en PHP kun nginx",
|
||||
"service_description_postfix": "Uzita por sendi kaj ricevi retpoŝtojn",
|
||||
"service_description_redis-server": "Specialita datumbazo uzita por rapida datumo atingo, atendovicoj kaj komunikadoj inter programoj",
|
||||
"service_description_rmilter": "Kontrolas diversajn parametrojn en retpoŝtoj",
|
||||
"service_description_rspamd": "Filtras trudmesaĝojn, kaj aliaj funkcioj rilate al retpoŝto",
|
||||
"service_description_slapd": "Stokas uzantojn, domajnojn kaj rilatajn informojn",
|
||||
"service_description_ssh": "Permesas al vi konekti al via servilo kun fora terminalo (SSH protokolo)",
|
||||
"service_description_yunohost-api": "Mastrumas interagojn inter la YunoHost retinterfaco kaj la sistemo",
|
||||
"service_description_yunohost-firewall": "Mastrumas malfermitajn kaj fermitajn konektejojn al servoj",
|
||||
"service_disable_failed": "Ne povis malŝalti la servon '{service:s}'\n\nFreŝaj protokoloj de la servo : {logs:s}",
|
||||
"service_disabled": "'{service: s}' servo malŝaltita",
|
||||
"action_invalid": "Nevalida ago « {action:s} »",
|
||||
"admin_password": "Pasvorto de la estro",
|
||||
"admin_password_too_long": "Bonvolu elekti pasvorton pli mallonga ol 127 signoj",
|
||||
"already_up_to_date": "Nenio por fari. Ĉio estas jam ĝisdatigita.",
|
||||
"app_argument_choice_invalid": "Uzu unu el ĉi tiuj elektoj '{choices:s}' por la argumento '{name:s}'",
|
||||
"app_argument_invalid": "Elektu validan valoron por la argumento '{name:s}': {error:s}",
|
||||
"app_change_url_failed_nginx_reload": "Ne eblis reŝarĝi NGINX. Jen la eligo de 'nginx -t':\n{nginx_errors:s}",
|
||||
"appslist_url_already_tracked": "Jam ekzistas registrita app-listo kun la URL {url:s}.",
|
||||
"ask_new_admin_password": "Nova administrada pasvorto",
|
||||
"app_action_broke_system": "Ĉi tiu ago ŝajne rompis ĉi tiujn gravajn servojn: {services}",
|
||||
"app_unsupported_remote_type": "Malkontrolita fora speco uzita por la apliko",
|
||||
"backup_archive_system_part_not_available": "Sistemo parto '{part:s}' ne haveblas en ĉi tiu rezervo",
|
||||
"apps_permission_not_found": "Neniu permeso trovita por la instalitaj programoj",
|
||||
"apps_permission_restoration_failed": "Donu la rajtigan permeson '{permission:s}' por restarigi {app:s}",
|
||||
"backup_abstract_method": "Ĉi tiu rezerva metodo ankoraŭ efektiviĝis",
|
||||
"apps_already_up_to_date": "Ĉiuj aplikoj estas jam ĝisdatigitaj",
|
||||
"backup_borg_not_implemented": "La kopia metodo de Borg ankoraŭ ne estas efektivigita",
|
||||
"app_upgrade_stopped": "Ĝisdatigi ĉiujn aplikaĵojn estis ĉesigita por eviti eblajn damaĝojn ĉar unu app ne povis esti altgradigita",
|
||||
"app_location_unavailable": "Ĉi tiu URL aŭ ne haveblas, aŭ konfliktas kun la jam instalita (j) apliko (j):\n{apps:s}",
|
||||
"backup_archive_app_not_found": "Ne povis trovi la programon '{app:s}' en la rezerva ar archiveivo",
|
||||
"backup_actually_backuping": "Krei rezervan ar archiveivon el la kolektitaj dosieroj …",
|
||||
"backup_method_borg_finished": "Sekurkopio en Borg finiĝis",
|
||||
"appslist_removed": "La listo de '{appslist:s}' estis forigita",
|
||||
"app_change_url_no_script": "La app '{app_name:s}' ankoraŭ ne subtenas URL-modifon. Eble vi devus altgradigi ĝin.",
|
||||
"app_start_install": "Instali la programon '{app}' …",
|
||||
"backup_created": "Sekurkopio kreita",
|
||||
"app_make_default_location_already_used": "Ne povas igi la aplikon '{app}' defaŭlta sur la domajno, '{domain}' jam uziĝas de la alia app '{other_app}'",
|
||||
"backup_method_copy_finished": "Rezerva kopio finis",
|
||||
"app_not_properly_removed": "{app:s} ne estis ĝuste forigita",
|
||||
"backup_archive_broken_link": "Ne povis aliri la rezervan ar archiveivon (rompita ligilo al {path:s})",
|
||||
"app_requirements_checking": "Kontrolante postulatajn pakaĵojn por {app} …",
|
||||
"app_not_installed": "Ne povis trovi la aplikon '{app:s}' en la listo de instalitaj programoj: {all_apps}",
|
||||
"app_location_install_failed": "Ne eblas instali la aplikon tie ĉar ĝi konfliktas kun la '{other_app}' jam instalita en '{other_path}'",
|
||||
"ask_new_path": "Nova vojo",
|
||||
"backup_custom_mount_error": "Propra rezerva metodo ne povis preterpasi la paŝon 'monto'",
|
||||
"app_upgrade_app_name": "Nun ĝisdatiganta {app} …",
|
||||
"app_manifest_invalid": "Io misas pri la aplika manifesto: {error}",
|
||||
"backup_cleaning_failed": "Ne povis purigi la provizoran rezervan dosierujon",
|
||||
"backup_invalid_archive": "Ĉi tio ne estas rezerva ar archiveivo",
|
||||
"ask_current_admin_password": "Pasvorto pri aktuala administrado",
|
||||
"backup_creation_failed": "Ne povis krei la rezervan ar archiveivon",
|
||||
"backup_hook_unknown": "La rezerva hoko '{hoko:s}' estas nekonata",
|
||||
"backup_custom_backup_error": "Propra rezerva metodo ne povis preterpasi la paŝon \"sekurkopio\"",
|
||||
"ask_main_domain": "Ĉefa domajno",
|
||||
"backup_method_tar_finished": "TAR-rezerva ar archiveivo kreita",
|
||||
"appslist_unknown": "La app-listo '{appslist:s}' estas nekonata.",
|
||||
"ask_list_to_remove": "Listo por forigi",
|
||||
"backup_cant_mount_uncompress_archive": "Ne povis munti la nekompresitan ar archiveivon kiel protektita kontraŭ skribo",
|
||||
"appslist_retrieve_bad_format": "Ne povis legi la elprenitan liston '{appslist:s}'",
|
||||
"appslist_corrupted_json": "Ne povis ŝarĝi la aplikajn listojn. Ĝi aspektas kiel {filename:s} estas damaĝita.",
|
||||
"app_action_cannot_be_ran_because_required_services_down": "Ĉi tiuj postulataj servoj devas funkcii por funkciigi ĉi tiun agon: {services}. Provu rekomenci ilin por daŭrigi (kaj eble esploru, kial ili malsupreniras).",
|
||||
"backup_copying_to_organize_the_archive": "Kopiante {size:s} MB por organizi la ar archiveivon",
|
||||
"backup_output_directory_forbidden": "Elektu malsaman elirejan dosierujon. Sekurkopioj ne povas esti kreitaj en sub-dosierujoj / bin, / boot, / dev, / ktp, / lib, / root, / run, / sbin, / sys, / usr, / var aŭ /home/yunohost.backup/archives",
|
||||
"appslist_could_not_migrate": "Ne povis migri la liston de aplikoj '{appslist:s}'! Ne eblis analizi la URL ... La malnova cron-laboro konserviĝis en {bkp_file:s}.",
|
||||
"app_requirements_failed": "Certaines exigences ne sont pas remplies pour {app}: {error}",
|
||||
"backup_no_uncompress_archive_dir": "Ne ekzistas tia nekompremita arkiva dosierujo",
|
||||
"password_too_simple_1": "Pasvorto devas esti almenaŭ 8 signojn longa",
|
||||
"app_upgrade_failed": "Ne povis ĝisdatigi {app:s}",
|
||||
"app_upgrade_several_apps": "La sekvaj apliko estos altgradigitaj: {apps}",
|
||||
"backup_archive_open_failed": "Ne povis malfermi la rezervan ar archiveivon",
|
||||
"ask_lastname": "Familia nomo",
|
||||
"app_start_backup": "Kolekti dosierojn por esti subtenata por la '{app}' …",
|
||||
"backup_archive_name_exists": "Rezerva arkivo kun ĉi tiu nomo jam ekzistas.",
|
||||
"backup_applying_method_tar": "Krei la rezervan TAR-ar archiveivon …",
|
||||
"backup_method_custom_finished": "Propra rezerva metodo '{metodo:s}' finiĝis",
|
||||
"appslist_retrieve_error": "Ne eblas akiri la forajn listojn '{appslist:s}': {eraro:s}",
|
||||
"app_already_installed_cant_change_url": "Ĉi tiu app estas jam instalita. La URL ne povas esti ŝanĝita nur per ĉi tiu funkcio. Rigardu \"app changeurl\" se ĝi haveblas.",
|
||||
"app_not_correctly_installed": "{app:s} ŝajnas esti malĝuste instalita",
|
||||
"app_removed": "{app:s} forigita",
|
||||
"backup_delete_error": "Ne povis forigi '{path: s}'",
|
||||
"app_package_need_update": "La pakaĵo {app} devas esti ĝisdatigita por sekvi YunoHost-ŝanĝojn",
|
||||
"backup_nothings_done": "Nenio por ŝpari",
|
||||
"backup_applying_method_custom": "Nomante la kutiman rezervan metodon '{metodo:s}' …",
|
||||
"appslist_fetched": "Ĝisdatigis la liston de aplikoj '{appslist:s}'",
|
||||
"backup_app_failed": "Ne eblis rezervi la programon '{app:s}'",
|
||||
"app_upgrade_some_app_failed": "Iuj aplikoj ne povis esti altgradigitaj",
|
||||
"app_start_remove": "Forigo de la apliko '{app}' …",
|
||||
"backup_output_directory_not_empty": "Vi devas elekti malplenan eligitan dosierujon",
|
||||
"backup_archive_writing_error": "Ne povis aldoni la dosierojn '{source:s}' (nomitaj en la ar theivo '{dest:s}') por esti rezervitaj en la kunpremita arkivo '{archive:s}'",
|
||||
"ask_email": "Retpoŝta adreso",
|
||||
"app_start_restore": "Restarigi la programon '{app}' …",
|
||||
"backup_applying_method_copy": "Kopiante ĉiujn dosierojn al sekurkopio …",
|
||||
"backup_couldnt_bind": "Ne povis ligi {src:s} al {dest:s}.",
|
||||
"ask_password": "Pasvorto",
|
||||
"app_requirements_unmeet": "Postuloj ne estas renkontitaj por {app}, la pakaĵo {pkgname} ({version}) devas esti {spec}",
|
||||
"ask_firstname": "Antaŭnomo",
|
||||
"backup_ask_for_copying_if_needed": "Ĉu vi volas realigi la sekurkopion uzante {size:s} MB provizore? (Ĉi tiu maniero estas uzata ĉar iuj dosieroj ne povus esti pretigitaj per pli efika metodo.)",
|
||||
"backup_mount_archive_for_restore": "Preparante arkivon por restarigo …",
|
||||
"appslist_migrating": "Migrado de la aplika listo '{appslist:s}' …",
|
||||
"backup_csv_creation_failed": "Ne povis krei la CSV-dosieron bezonatan por restarigo",
|
||||
"backup_archive_name_unknown": "Nekonata loka rezerva ar archiveivo nomata '{name:s}'",
|
||||
"backup_applying_method_borg": "Sendado de ĉiuj dosieroj al sekurkopio en borg-rezerva deponejo …",
|
||||
"app_sources_fetch_failed": "Ne povis akiri fontajn dosierojn, ĉu la URL estas ĝusta?",
|
||||
"appslist_name_already_tracked": "Registrita aplika listo kun la nomo {name:s} jam ekzistas.",
|
||||
"ask_new_domain": "Nova domajno",
|
||||
"app_unknown": "Nekonata apliko",
|
||||
"app_not_upgraded": "La aplikaĵo '{failed_app}' ne ĝisdatigis, kaj pro tio la sekvaj ĝisdatigoj de aplikoj estis nuligitaj: {apps}",
|
||||
"aborting": "Aborti.",
|
||||
"ask_path": "Pado",
|
||||
"app_upgraded": "{app:s} altgradigita",
|
||||
"backup_deleted": "Rezerva forigita",
|
||||
"backup_csv_addition_failed": "Ne povis aldoni dosierojn al sekurkopio en la CSV-dosiero",
|
||||
"dpkg_lock_not_available": "Ĉi tiu komando ne povas funkcii nun ĉar alia programo uzas la seruron de dpkg (la administrilo de paka sistemo)",
|
||||
"migration_0003_yunohost_upgrade": "Komenci la ĝisdatigon de YunoHost-pako ... La migrado finiĝos, sed la efektiva ĝisdatigo okazos tuj poste. Post kiam la operacio finiĝos, vi eble devos ensaluti denove sur la retpaĝo.",
|
||||
"domain_dyndns_root_unknown": "Nekonata radika domajno DynDNS",
|
||||
"field_invalid": "Nevalida kampo '{:s}'",
|
||||
"log_app_makedefault": "Faru '{}' la defaŭlta apliko",
|
||||
"migration_0003_still_on_jessie_after_main_upgrade": "Io okazis malbone dum la ĉefa ĝisdatigo: Ĉu la sistemo ankoraŭ estas en Jessie‽ Por esplori la aferon, bonvolu rigardi {log}:s …",
|
||||
"migration_0011_can_not_backup_before_migration": "La sekurkopio de la sistemo ne povis finiĝi antaŭ ol la migrado malsukcesis. Eraro: {error:s}",
|
||||
"migration_0011_create_group": "Krei grupon por ĉiu uzanto…",
|
||||
"backup_system_part_failed": "Ne eblis sekurkopi la sistemon de '{part:s}'",
|
||||
"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:s}' estas nekonata",
|
||||
"mailbox_disabled": "Retpoŝto malŝaltita por uzanto {user:s}",
|
||||
"migration_description_0011_setup_group_permission": "Agordu uzantogrupon kaj starigu permeson por programoj kaj servoj",
|
||||
"migration_0011_backup_before_migration": "Krei sekurkopion de LDAP-datumbazo kaj agordojn antaŭ la efektiva migrado.",
|
||||
"migration_0011_LDAP_config_dirty": "Similas ke vi agordis vian LDAP-agordon. Por ĉi tiu migrado la LDAP-agordo bezonas esti ĝisdatigita.\nVi devas konservi vian aktualan agordon, reintaligi la originalan agordon per funkciado de \"yunohost iloj regen-conf -f\" kaj reprovi la migradon",
|
||||
"migration_0011_migrate_permission": "Migrado de permesoj de agordoj al aplikoj al LDAP…",
|
||||
"migration_0011_migration_failed_trying_to_rollback": "Ne povis migri ... provante redakti la sistemon.",
|
||||
"migrations_dependencies_not_satisfied": "Rulu ĉi tiujn migradojn: '{dependencies_id}', antaŭ migrado {id}.",
|
||||
"migrations_failed_to_load_migration": "Ne povis ŝarĝi migradon {id}: {error}",
|
||||
"migrations_exclusive_options": "'--auto', '--skip' kaj '--force-rerun' estas reciproke ekskluzivaj ebloj.",
|
||||
"migrations_must_provide_explicit_targets": "Vi devas provizi eksplicitajn celojn kiam vi uzas '--skip' aŭ '--force-rerun'",
|
||||
"permission_update_failed": "Ne povis ĝisdatigi permeson '{permission}': {error}",
|
||||
"permission_updated": "Ĝisdatigita \"{permission:s}\" rajtigita",
|
||||
"permission_update_nothing_to_do": "Neniuj permesoj ĝisdatigi",
|
||||
"tools_upgrade_cant_hold_critical_packages": "Ne povis teni kritikajn pakojn…",
|
||||
"upnp_dev_not_found": "Neniu UPnP-aparato trovita",
|
||||
"migration_description_0012_postgresql_password_to_md5_authentication": "Devigu PostgreSQL-aŭtentigon uzi MD5 por lokaj ligoj",
|
||||
"migration_0011_done": "Migrado finiĝis. Vi nun kapablas administri uzantajn grupojn.",
|
||||
"migration_0011_LDAP_update_failed": "Ne povis ĝisdatigi LDAP. Eraro: {error:s}",
|
||||
"pattern_password": "Devas esti almenaŭ 3 signoj longaj",
|
||||
"root_password_desynchronized": "La pasvorta administranto estis ŝanĝita, sed YunoHost ne povis propagandi ĉi tion al la radika pasvorto!",
|
||||
"service_remove_failed": "Ne povis forigi la servon '{service:s}'",
|
||||
"migration_0003_fail2ban_upgrade": "Komenci la ĝisdatigon Fail2Ban…",
|
||||
"backup_permission": "Rezerva permeso por app {app:s}",
|
||||
"log_user_group_delete": "Forigi grupon '{}'",
|
||||
"log_user_group_update": "Ĝisdatigi grupon '{}'",
|
||||
"migration_0005_postgresql_94_not_installed": "PostgreSQL ne estis instalita en via sistemo. Nenio por fari.",
|
||||
"dyndns_provider_unreachable": "Ne povas atingi Dyndns-provizanton {provider}: ĉu via YunoHost ne estas ĝuste konektita al la interreto aŭ la dynette-servilo malŝaltiĝas.",
|
||||
"good_practices_about_user_password": "Vi nun estas por difini novan uzantan pasvorton. La pasvorto devas esti almenaŭ 8 signoj - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj / aŭ variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).",
|
||||
"group_updated": "Ĝisdatigita \"{group}\" grupo",
|
||||
"group_already_exist": "Grupo {group} jam ekzistas",
|
||||
"group_already_exist_on_system": "Grupo {group} jam ekzistas en la sistemaj grupoj",
|
||||
"group_cannot_be_edited": "La grupo {group} ne povas esti redaktita permane.",
|
||||
"group_cannot_be_deleted": "La grupo {group} ne povas esti forigita permane.",
|
||||
"group_update_failed": "Ne povis ĝisdatigi la grupon '{group}': {error}",
|
||||
"group_user_already_in_group": "Uzanto {user} jam estas en grupo {group}",
|
||||
"group_user_not_in_group": "Uzanto {user} ne estas en grupo {group}",
|
||||
"installation_complete": "Kompleta instalado",
|
||||
"log_category_404": "La loga kategorio '{category}' ne ekzistas",
|
||||
"log_permission_create": "Krei permeson '{}'",
|
||||
"log_permission_delete": "Forigi permeson '{}'",
|
||||
"log_permission_urls": "Ĝisdatigu URLojn rilatajn al permeso '{}'",
|
||||
"log_user_group_create": "Krei grupon '{}'",
|
||||
"log_user_permission_update": "Mise à jour des accès pour la permission '{}'",
|
||||
"log_user_permission_reset": "Restarigi permeson '{}'",
|
||||
"mail_forward_remove_failed": "Ne povis forigi retpoŝton plusendante '{mail:s}'",
|
||||
"migration_0011_rollback_success": "Sistemo ruliĝis reen.",
|
||||
"migration_0011_update_LDAP_database": "Ĝisdatigante LDAP-datumbazon…",
|
||||
"migration_0011_update_LDAP_schema": "Ĝisdatigante LDAP-skemon…",
|
||||
"migration_0011_failed_to_remove_stale_object": "Ne povis forigi neuzatan objekton {dn}: {error}",
|
||||
"migrations_already_ran": "Tiuj migradoj estas jam faritaj: {ids}",
|
||||
"migrations_no_such_migration": "Estas neniu migrado nomata '{id}'",
|
||||
"permission_already_allowed": "Grupo '{group}' jam havas permeson '{permission}' ebligita'",
|
||||
"permission_already_disallowed": "Grupo '{group}' jam havas permeson '{permission}' malebligita'",
|
||||
"permission_cannot_remove_main": "Forigo de ĉefa permeso ne rajtas",
|
||||
"permission_creation_failed": "Ne povis krei permeson '{permission}': {error}",
|
||||
"tools_update_failed_to_app_fetchlist": "Ne povis ĝisdatigi la aparatojn de YunoHost ĉar: {error}",
|
||||
"user_already_exists": "Uzanto {uzanto} jam ekzistas",
|
||||
"migrations_pending_cant_rerun": "Tiuj migradoj ankoraŭ estas pritraktataj, do ne plu rajtas esti ekzekutitaj: {ids}",
|
||||
"migrations_running_forward": "Kuranta migrado {id}…",
|
||||
"migrations_success_forward": "Migrado {id} kompletigita",
|
||||
"operation_interrupted": "La operacio estis permane interrompita?",
|
||||
"permission_created": "Permesita '{permission:s}' kreita",
|
||||
"permission_deleted": "Permesita \"{permission:s}\" forigita",
|
||||
"permission_deletion_failed": "Ne povis forigi permeson '{permission}': {error}",
|
||||
"permission_not_found": "Permesita \"{permission:s}\" ne trovita",
|
||||
"restore_not_enough_disk_space": "Ne sufiĉa spaco (spaco: {free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)",
|
||||
"tools_upgrade_regular_packages": "Nun ĝisdatigi 'regulajn' (ne-yunohost-rilatajn) pakojn …",
|
||||
"tools_upgrade_special_packages_explanation": "Ĉi tiu ago finiĝos, sed la fakta speciala ĝisdatigo daŭros en fono. Bonvolu ne komenci iun alian agon en via servilo en la sekvaj ~ 10 minutoj (depende de via aparata rapideco). Unufoje mi plenumis, vi eble devos ensaluti en la retpaĝo. La ĝisdatiga registro estos havebla en Iloj → Madero (sur la retpaĝo) aŭ tra 'yunohost-registro-listo' (el la komandlinio).",
|
||||
"unrestore_app": "App '{app:s}' ne restarigos",
|
||||
"group_created": "Grupo '{group}' kreita",
|
||||
"group_creation_failed": "Ne povis krei la grupon '{group}': {error}",
|
||||
"group_deleted": "Grupo '{group}' forigita",
|
||||
"group_deletion_failed": "Ne povis forigi la grupon '{group}': {error}",
|
||||
"migrations_not_pending_cant_skip": "Tiuj migradoj ankoraŭ ne estas pritraktataj, do ne eblas preterlasi: {ids}",
|
||||
"permission_already_exist": "Permesita '{permission}' jam ekzistas",
|
||||
"domain_created": "Domajno kreita",
|
||||
"migrate_tsig_wait_2": "2 minutoj …",
|
||||
"log_user_create": "Aldonu uzanton '{}'",
|
||||
"ip6tables_unavailable": "Vi ne povas ludi kun ip6tabloj ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin",
|
||||
"mail_unavailable": "Ĉi tiu retpoŝta adreso estas rezervita kaj aŭtomate estos atribuita al la unua uzanto",
|
||||
"certmanager_domain_dns_ip_differs_from_public_ip": "La DNS 'A' rekordo por la domajno '{domain:s}' diferencas de ĉi tiu IP-servilo. Se vi lastatempe modifis vian A-registron, bonvolu atendi ĝin propagandi (iuj DNS-disvastigaj kontroliloj estas disponeblaj interrete). (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)",
|
||||
"tools_upgrade_special_packages_completed": "Plenumis la ĝisdatigon de pakaĵoj de YunoHost.\nPremu [Enter] por retrovi la komandlinion",
|
||||
"log_remove_on_failed_install": "Forigu '{}' post malsukcesa instalado",
|
||||
"regenconf_file_manually_modified": "La agorddosiero '{conf}' estis modifita permane kaj ne estos ĝisdatigita",
|
||||
"regenconf_would_be_updated": "La agordo estus aktualigita por la kategorio '{category}'",
|
||||
"certmanager_cert_install_success_selfsigned": "Mem-subskribita atestilo nun instalita por la domajno '{domain:s}'",
|
||||
"global_settings_unknown_setting_from_settings_file": "Nekonata ŝlosilo en agordoj: '{setting_key:s}', forĵetu ĝin kaj konservu ĝin en /etc/yunohost/settings-unknown.json",
|
||||
"regenconf_file_backed_up": "Agordodosiero '{conf}' estis rezervita al '{backup}'",
|
||||
"migration_0007_cannot_restart": "SSH ne rekomencas post provi nuligi la migradan numeron 6.",
|
||||
"migration_description_0006_sync_admin_and_root_passwords": "Sinkronigu admin kaj radikajn pasvortojn",
|
||||
"updating_app_lists": "Akirante haveblajn ĝisdatigojn por aplikoj…",
|
||||
"iptables_unavailable": "Vi ne povas ludi kun iptables ĉi tie. Vi estas en ujo aŭ via kerno ne subtenas ĝin",
|
||||
"global_settings_cant_write_settings": "Ne eblis konservi agordojn, tial: {reason:s}",
|
||||
"service_added": "La servo '{service:s}' aldonis",
|
||||
"upnp_disabled": "UPnP malŝaltis",
|
||||
"service_started": "Servo '{service:s}' komenciĝis",
|
||||
"port_already_opened": "Haveno {port:d} estas jam malfermita por {ip_version:s} rilatoj",
|
||||
"installation_failed": "Io okazis malbone kun la instalado",
|
||||
"network_check_mx_ko": "DNS MX-rekordo ne estas agordita",
|
||||
"migrate_tsig_wait_3": "1 minuto …",
|
||||
"certmanager_conflicting_nginx_file": "Ne povis prepari domajnon por ACME-defio: la agordo de NGINX {filepath:s} konfliktas kaj unue devas esti forigita",
|
||||
"upgrading_packages": "Ĝisdatigi pakojn…",
|
||||
"custom_app_url_required": "Vi devas provizi URL por altgradigi vian kutimon app {app: s}",
|
||||
"service_reload_failed": "Ne povis reŝargi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}",
|
||||
"packages_upgrade_failed": "Ne povis ĝisdatigi ĉiujn pakojn",
|
||||
"hook_json_return_error": "Ne povis legi revenon de hoko {path:s}. Eraro: {msg:s}. Kruda enhavo: {raw_content}",
|
||||
"dyndns_cron_removed": "DynDNS cron-laboro forigita",
|
||||
"dyndns_key_not_found": "DNS-ŝlosilo ne trovita por la domajno",
|
||||
"custom_appslist_name_required": "Vi devas doni nomon por via kutima app-listo",
|
||||
"tools_upgrade_regular_packages_failed": "Ne povis ĝisdatigi pakojn: {packages_list}",
|
||||
"service_start_failed": "Ne povis komenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}",
|
||||
"service_reloaded": "Servo '{service:s}' reŝargita",
|
||||
"system_upgraded": "Sistemo ĝisdatigita",
|
||||
"domain_deleted": "Domajno forigita",
|
||||
"certmanager_acme_not_configured_for_domain": "Atestilo por la domajno '{domain:s}' ne ŝajnas esti ĝuste instalita. Bonvolu ekzekuti 'cert-instali' por ĉi tiu regado unue.",
|
||||
"migration_description_0009_decouple_regenconf_from_services": "Malkonstruu la regen-konf-mekanismon de servoj",
|
||||
"user_update_failed": "Ne povis ĝisdatigi uzanton {user}: {error}",
|
||||
"migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Lasu la SSH-agordon estu administrata de YunoHost (paŝo 2, manlibro)",
|
||||
"restore_confirm_yunohost_installed": "Ĉu vi vere volas restarigi jam instalitan sistemon? [{answers:s}]",
|
||||
"pattern_positive_number": "Devas esti pozitiva nombro",
|
||||
"monitor_stats_file_not_found": "Ne povis trovi la statistikan dosieron",
|
||||
"certmanager_error_no_A_record": "Neniu DNS 'A' rekordo trovita por '{domain:s}'. Vi bezonas atentigi vian domajnan nomon al via maŝino por povi instali atestilon Lasu-Ĉifri. (Se vi scias, kion vi faras, uzu '--no-checks' por malŝalti tiujn ĉekojn.)",
|
||||
"update_apt_cache_failed": "Ne eblis ĝisdatigi la kaŝmemoron de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}",
|
||||
"migrations_no_migrations_to_run": "Neniuj migradoj por funkcii",
|
||||
"executing_command": "Plenumanta komandon '{command:s}' …",
|
||||
"diagnosis_no_apps": "Neniu tia instalita app",
|
||||
"certmanager_attempt_to_renew_nonLE_cert": "La atestilo por la domajno '{domain:s}' ne estas elsendita de Let's Encrypt. Ne eblas renovigi ĝin aŭtomate!",
|
||||
"global_settings_setting_example_bool": "Ekzemplo bulea elekto",
|
||||
"domain_dyndns_already_subscribed": "Vi jam abonis DynDNS-domajnon",
|
||||
"log_letsencrypt_cert_renew": "Renovigu '{}' Let's Encrypt atestilon",
|
||||
"migrate_tsig_start": "Detektita ŝlosila algoritmo nesufiĉa por TSIG-subskribo de la domajno '{domain}', komencanta migradon al la pli sekura HMAC-SHA-512",
|
||||
"ldap_init_failed_to_create_admin": "LDAP-iniciato ne povis krei administran uzanton",
|
||||
"backup_output_directory_required": "Vi devas provizi elirejan dosierujon por la sekurkopio",
|
||||
"tools_upgrade_cant_unhold_critical_packages": "Ne povis malhelpi kritikajn pakojn…",
|
||||
"diagnosis_monitor_disk_error": "Ne povis monitori diskojn: {error}",
|
||||
"log_link_to_log": "Plena ŝtipo de ĉi tiu operacio: '<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\"> {desc} </a>'",
|
||||
"service_no_log": "Neniu registro por montri por servo '{service:s}'",
|
||||
"global_settings_cant_serialize_settings": "Ne eblis serialigi datumojn pri agordoj, motivo: {reason:s}",
|
||||
"backup_running_hooks": "Kurado de apogaj hokoj …",
|
||||
"package_not_installed": "Pako '{pkgname}' ne estas instalita",
|
||||
"certmanager_domain_unknown": "Nekonata domajno '{domain:s}'",
|
||||
"unexpected_error": "Io neatendita iris malbone: {error}",
|
||||
"password_listed": "Ĉi tiu pasvorto estas inter la plej uzataj pasvortoj en la mondo. Bonvolu elekti ion pli unikan.",
|
||||
"ssowat_persistent_conf_write_error": "Ne povis konservi konstantan SSOwat-agordon: {error:s}. Redakti /etc/ssowat/conf.json.persistent dosiero por ripari la Jaks-sintakson",
|
||||
"migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Lasu la SSH-agordon estu administrata de YunoHost (paŝo 1, aŭtomata)",
|
||||
"migration_0009_not_needed": "Ĉi tiu migrado jam iel okazis ... (?) Saltado.",
|
||||
"ssowat_conf_generated": "SSOwat-agordo generita",
|
||||
"migrate_tsig_wait": "Atendante tri minutojn por ke la servilo DynDNS enkalkulu la novan ŝlosilon …",
|
||||
"log_remove_on_failed_restore": "Forigu '{}' post malsukcesa restarigo de rezerva ar archiveivo",
|
||||
"dpkg_is_broken": "Vi ne povas fari ĉi tion nun ĉar dpkg/APT (la administrantoj pri pakaĵaj sistemoj) ŝajnas esti rompita stato ... Vi povas provi solvi ĉi tiun problemon per konekto per SSH kaj funkcianta `sudo dpkg --configure -a`.",
|
||||
"recommend_to_add_first_user": "La postinstalo finiĝis, sed YunoHost bezonas almenaŭ unu uzanton por funkcii ĝuste, vi devas aldoni unu uzante 'yunohost user create <username>' aŭ fari ĝin de la administra interfaco.",
|
||||
"certmanager_cert_signing_failed": "Ne povis subskribi la novan atestilon",
|
||||
"migration_description_0003_migrate_to_stretch": "Altgradigu la sistemon al Debian Stretch kaj YunoHost 3.0",
|
||||
"log_tools_upgrade": "Ĝisdatigu sistemajn pakaĵojn",
|
||||
"network_check_smtp_ko": "Ekstera retpoŝto (SMTP-haveno 25) ŝajnas esti blokita de via reto",
|
||||
"log_available_on_yunopaste": "Ĉi tiu protokolo nun haveblas per {url}",
|
||||
"certmanager_http_check_timeout": "Ekdifinita kiam servilo provis kontakti sin per HTTP per publika IP-adreso (domajno '{domain:s}' kun IP '{ip:s}'). Vi eble spertas haŭtoproblemon, aŭ la fajroŝirmilo / enkursigilo antaŭ via servilo miskonfiguras.",
|
||||
"pattern_port_or_range": "Devas esti valida haveno-nombro (t.e. 0-65535) aŭ gamo da havenoj (t.e. 100:200)",
|
||||
"migrations_loading_migration": "Ŝarĝante migradon {id}…",
|
||||
"port_available": "Haveno {port:d} estas havebla",
|
||||
"pattern_mailbox_quota": "Devas esti grandeco kun la sufikso b/k/M/G/T aŭ 0 por ne havi kvoton",
|
||||
"migration_0008_general_disclaimer": "Por plibonigi la sekurecon de via servilo, rekomendas lasi YunoHost administri la SSH-agordon. Via nuna SSH-aranĝo diferencas de la rekomendo. Se vi lasas YunoHost agordi ĝin, la maniero per kiu vi konektas al via servilo per SSH ŝanĝiĝos tiel:",
|
||||
"user_deletion_failed": "Ne povis forigi uzanton {user}: {error}",
|
||||
"backup_with_no_backup_script_for_app": "La app '{app:s}' ne havas sekretan skripton. Ignorante.",
|
||||
"service_regen_conf_is_deprecated": "'yunohost service regen-conf' malakceptas! Bonvolu uzi anstataŭe 'yunohost tools regen-conf'.",
|
||||
"global_settings_key_doesnt_exists": "La ŝlosilo '{settings_key:s}' ne ekzistas en la tutmondaj agordoj, vi povas vidi ĉiujn disponeblajn klavojn per uzado de 'yunohost settings list'",
|
||||
"dyndns_no_domain_registered": "Neniu domajno registrita ĉe DynDNS",
|
||||
"dyndns_could_not_check_available": "Ne povis kontroli ĉu {domain:s} haveblas sur {provider:s}.",
|
||||
"log_app_removelist": "Forigu aplikan liston",
|
||||
"global_settings_setting_example_enum": "Ekzemplo enum elekto",
|
||||
"hook_exec_not_terminated": "Skripto ne finiĝis ĝuste: {path:s}",
|
||||
"service_stopped": "'{service:s}' servo ĉesis",
|
||||
"restore_failed": "Ne povis restarigi sistemon",
|
||||
"confirm_app_install_danger": "Danĝero! Ĉi tiu apliko estas konata ankoraŭ eksperimenta (se ne eksplicite ne funkcias)! 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: s}'",
|
||||
"log_operation_unit_unclosed_properly": "Operaciumo ne estis fermita ĝuste",
|
||||
"upgrade_complete": "Ĝisdatigo kompleta",
|
||||
"upnp_enabled": "UPnP ŝaltis",
|
||||
"mailbox_used_space_dovecot_down": "La retpoŝta servo de Dovecot devas funkcii, se vi volas akcepti uzitan poŝtan spacon",
|
||||
"restore_system_part_failed": "Ne povis restarigi la sisteman parton '{part:s}'",
|
||||
"diagnosis_monitor_system_error": "Ne povis monitori sistemon: {error}",
|
||||
"service_stop_failed": "Ne povis maldaŭrigi la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}",
|
||||
"unbackup_app": "App '{app:s}' ne konserviĝos",
|
||||
"updating_apt_cache": "Akirante haveblajn ĝisdatigojn por sistemaj pakoj…",
|
||||
"tools_upgrade_at_least_one": "Bonvolu specifi '--apps' aŭ '--system'",
|
||||
"service_already_stopped": "La servo '{service:s}' jam ĉesis",
|
||||
"unit_unknown": "Nekonata unuo '{unit:s}'",
|
||||
"migration_0003_modified_files": "Bonvolu noti, ke la jenaj dosieroj estis trovitaj mane kaj modifitaj kaj povus esti anstataŭigitaj sekve de la ĝisdatigo: {manual_modified_files}",
|
||||
"tools_upgrade_cant_both": "Ne eblas ĝisdatigi ambaŭ sistemon kaj programojn samtempe",
|
||||
"restore_extracting": "Eltirante bezonatajn dosierojn el la ar theivo…",
|
||||
"upnp_port_open_failed": "Ne povis malfermi havenon per UPnP",
|
||||
"log_app_upgrade": "Ĝisdatigu la aplikon '{}'",
|
||||
"log_help_to_get_failed_log": "La operacio '{desc}' ne povis finiĝi. Bonvolu dividi la plenan ŝtipon de ĉi tiu operacio per la komando 'yunohost log display {name} --share' por akiri helpon",
|
||||
"migration_description_0002_migrate_to_tsig_sha256": "Plibonigu sekurecon de DynDNS TSIG-ĝisdatigoj per SHA-512 anstataŭ MD5",
|
||||
"monitor_disabled": "Servilo-monitorado nun malŝaltita",
|
||||
"pattern_port": "Devas esti valida havena numero (t.e. 0-65535)",
|
||||
"port_already_closed": "Haveno {port:d} estas jam fermita por {ip_version:s} rilatoj",
|
||||
"hook_name_unknown": "Nekonata hoko-nomo '{name:s}'",
|
||||
"migration_0003_system_not_fully_up_to_date": "Via sistemo ne estas plene ĝisdata. Bonvolu plenumi regulan ĝisdatigon antaŭ ol ruli la migradon al Stretch.",
|
||||
"dyndns_could_not_check_provide": "Ne povis kontroli ĉu {provider:s} povas provizi {domain:s}.",
|
||||
"dyndns_cron_remove_failed": "Ne povis forigi la cron-laboron DynDNS ĉar: {error}",
|
||||
"pattern_listname": "Devas esti nur alfanumeraj kaj substrekaj signoj",
|
||||
"restore_nothings_done": "Nenio estis restarigita",
|
||||
"log_tools_postinstall": "Afiŝu vian servilon YunoHost",
|
||||
"dyndns_unavailable": "La domajno '{domain:s}' 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.",
|
||||
"root_password_replaced_by_admin_password": "Via radika pasvorto estis anstataŭigita per via administra pasvorto.",
|
||||
"ssowat_persistent_conf_read_error": "Ne povis legi konstantan SSOwat-agordon: {error:s}. Redakti /etc/ssowat/conf.json.persistent dosiero por ripari la Jaks-sintakson",
|
||||
"migration_description_0005_postgresql_9p4_to_9p6": "Migru datumbazojn de PostgreSQL 9.4 al 9.6",
|
||||
"migration_0008_root": "• Vi ne povos konekti kiel radiko per SSH. Anstataŭe vi uzu la administran uzanton;",
|
||||
"package_unknown": "Nekonata pako '{pkgname}'",
|
||||
"domain_unknown": "Nekonata domajno",
|
||||
"global_settings_setting_security_password_user_strength": "Uzanto pasvorta forto",
|
||||
"restore_may_be_not_enough_disk_space": "Via sistemo ŝajnas ne havi sufiĉe da spaco (free:{free_space:d} B, necesa spaco: {needed_space:d} B, sekureca marĝeno: {margin:d} B)",
|
||||
"log_corrupted_md_file": "La YAD-metadata dosiero asociita kun protokoloj estas damaĝita: '{md_file}\nEraro: {error} '",
|
||||
"downloading": "Elŝutante …",
|
||||
"user_deleted": "Uzanto forigita",
|
||||
"service_enable_failed": "Ne eblis ŝalti la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}",
|
||||
"tools_upgrade_special_packages": "Nun ĝisdatigi 'specialajn' (rilatajn al yunohost)…",
|
||||
"domains_available": "Haveblaj domajnoj:",
|
||||
"dyndns_registered": "Registrita domajno DynDNS",
|
||||
"service_description_fail2ban": "Protektas kontraŭ bruta forto kaj aliaj specoj de atakoj de la interreto",
|
||||
"file_does_not_exist": "La dosiero {path:s} ne ekzistas.",
|
||||
"yunohost_not_installed": "YunoHost estas malĝuste aŭ ne ĝuste instalita. Bonvolu prilabori 'yunohost tools postinstall'",
|
||||
"migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 estas instalita, sed ne postgresql 9.6‽ Io stranga eble okazis en via sistemo: (…",
|
||||
"restore_removing_tmp_dir_failed": "Ne povis forigi malnovan provizoran dosierujon",
|
||||
"certmanager_cannot_read_cert": "Io malbona okazis, kiam mi provis malfermi aktualan atestilon por domajno {domain:s} (dosiero: {file:s}), kialo: {reason:s}",
|
||||
"service_removed": "'{service:s}' servo forigita",
|
||||
"certmanager_hit_rate_limit": "Tro multaj atestiloj jam eldonitaj por ĉi tiu ĝusta aro de domajnoj {domain:s} antaŭ nelonge. Bonvolu reprovi poste. Vidu https://letsencrypt.org/docs/rate-limits/ por pliaj detaloj",
|
||||
"migration_0005_not_enough_space": "Disponigu sufiĉan spacon en {path} por ruli la migradon.",
|
||||
"pattern_firstname": "Devas esti valida antaŭnomo",
|
||||
"migration_description_0010_migrate_to_apps_json": "Forigu malvalorigitajn aparatojn kaj uzu anstataŭe la novan unuigitan liston \"apps.json\"",
|
||||
"domain_cert_gen_failed": "Ne povis generi atestilon",
|
||||
"regenconf_file_kept_back": "La agorda dosiero '{conf}' estas atendita forigi per regen-conf (kategorio {category}), sed ĝi estis konservita.",
|
||||
"migrate_tsig_wait_4": "30 sekundoj …",
|
||||
"backup_with_no_restore_script_for_app": "La apliko \"{app:s}\" ne havas restarigan skripton, vi ne povos aŭtomate restarigi la sekurkopion de ĉi tiu apliko.",
|
||||
"log_letsencrypt_cert_install": "Instalu atestilon Let's Encrypt sur '{}' regado",
|
||||
"log_dyndns_update": "Ĝisdatigu la IP asociita kun via subdominio YunoHost '{}'",
|
||||
"firewall_reload_failed": "Ne eblis reŝargi la firewall",
|
||||
"confirm_app_install_warning": "Averto: Ĉi tiu aplikaĵo povas funkcii, sed ne bone integras en YunoHost. Iuj funkcioj kiel ekzemple aliĝilo kaj sekurkopio / restarigo eble ne haveblos. Instali ĉiuokaze? [{answers: s}] ",
|
||||
"log_user_delete": "Forigi uzanton '{}'",
|
||||
"dyndns_ip_updated": "Ĝisdatigis vian IP sur DynDNS",
|
||||
"regenconf_up_to_date": "La agordo jam estas ĝisdatigita por kategorio '{category}'",
|
||||
"migration_0003_patching_sources_list": "Patching the sources.lists …",
|
||||
"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{malavantaĝo}\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}'",
|
||||
"not_enough_disk_space": "Ne sufiĉe libera spaco sur '{path:s}'",
|
||||
"migration_0006_disclaimer": "YunoHost nun atendas, ke la pasvortoj de admin kaj radiko estos sinkronigitaj. Ĉi tiu migrado anstataŭigas vian radikan pasvorton kun la administran pasvorton.",
|
||||
"dyndns_ip_update_failed": "Ne povis ĝisdatigi IP-adreson al DynDNS",
|
||||
"migration_description_0004_php5_to_php7_pools": "Rekonfigu la PHP-naĝejojn por uzi PHP 7 anstataŭ 5",
|
||||
"monitor_glances_con_failed": "Ne povis konektiĝi al servilo de Glances",
|
||||
"ssowat_conf_updated": "SSOwat-agordo ĝisdatigita",
|
||||
"log_link_to_failed_log": "Ne povis plenumi la operacion '{desc}'. Bonvolu provizi la plenan protokolon de ĉi tiu operacio per <a href=\"#/tools/logs/{name}\"> alklakante ĉi tie </a> por akiri helpon",
|
||||
"log_app_fetchlist": "Aldonu liston de aplikoj",
|
||||
"user_home_creation_failed": "Ne povis krei dosierujon \"home\" por uzanto",
|
||||
"pattern_backup_archive_name": "Devas esti valida dosiernomo kun maksimume 30 signoj, alfanombraj kaj -_. signoj nur",
|
||||
"restore_cleaning_failed": "Ne eblis purigi la adresaron de provizora restarigo",
|
||||
"dyndns_registration_failed": "Ne povis registri DynDNS-domajnon: {error:s}",
|
||||
"migration_0003_not_jessie": "La nuna Debian-distribuo ne estas Jessie!",
|
||||
"user_unknown": "Nekonata uzanto: {user:s}",
|
||||
"migrations_to_be_ran_manually": "Migrado {id} devas funkcii permane. Bonvolu iri al Iloj → Migradoj en la retpaĝa paĝo, aŭ kuri `yunohost tools migrations migrate`.",
|
||||
"migration_0008_warning": "Se vi komprenas tiujn avertojn kaj volas ke YunoHost preterlasu vian nunan agordon, faru la migradon. Alie, vi ankaŭ povas salti la migradon, kvankam ĝi ne rekomendas.",
|
||||
"certmanager_cert_renew_success": "Ni Ĉifru atestilon renovigitan por la domajno '{domain:s}'",
|
||||
"global_settings_reset_success": "Antaŭaj agordoj nun estas rezervitaj al {path:s}",
|
||||
"pattern_domain": "Devas esti valida domajna nomo (t.e. mia-domino.org)",
|
||||
"package_unexpected_error": "Neatendita eraro okazis prilaborante la pakon '{pkgname}'",
|
||||
"dyndns_key_generating": "Generi DNS-ŝlosilon ... Eble daŭros iom da tempo.",
|
||||
"restore_running_app_script": "Restarigi la programon '{app:s}'…",
|
||||
"migrations_skip_migration": "Salti migradon {id}…",
|
||||
"mysql_db_init_failed": "MysQL-datumbazo init malsukcesis",
|
||||
"regenconf_file_removed": "Agordodosiero '{conf}' forigita",
|
||||
"log_tools_shutdown": "Enŝaltu vian servilon",
|
||||
"password_too_simple_3": "La pasvorto bezonas almenaŭ 8 signojn kaj enhavas ciferon, majusklon, pli malaltan kaj specialajn signojn",
|
||||
"migration_0003_general_warning": "Bonvolu noti, ke ĉi tiu migrado estas delikata operacio. La teamo de YunoHost faris sian plej bonan revizii kaj testi ĝin, sed la migrado eble ankoraŭ rompos partojn de la sistemo aŭ ĝiaj programoj.\n\nTial oni rekomendas al:\n - Elfari kopion de iuj kritikaj datumoj aŭ app. Pliaj informoj pri https://yunohost.org/backup;\n - Paciencu post lanĉo de la migrado: Depende de via interreta konekto kaj aparataro, eble daŭros kelkaj horoj ĝis ĉio ĝisdatigi.\n\nAldone, la haveno por SMTP, uzata de eksteraj retpoŝtaj klientoj (kiel Thunderbird aŭ K9-Mail) estis ŝanĝita de 465 (SSL / TLS) al 587 (STARTTLS). La malnova haveno (465) aŭtomate fermiĝos, kaj la nova haveno (587) malfermiĝos en la fajrejo. Vi kaj viaj uzantoj * devos adapti la agordon de viaj retpoŝtaj klientoj laŭe.",
|
||||
"diagnosis_kernel_version_error": "Ne povis akiri la kernan version: {error}",
|
||||
"global_settings_setting_example_int": "Ekzemple int elekto",
|
||||
"backup_output_symlink_dir_broken": "Via arkiva dosierujo '{path:s}' estas rompita ligilo. Eble vi forgesis restarigi aŭ munti aŭ enŝovi la stokadon, al kiu ĝi notas.",
|
||||
"good_practices_about_admin_password": "Vi nun estas por difini novan administran pasvorton. La pasvorto devas esti almenaŭ 8 signoj - kvankam estas bone praktiki uzi pli longan pasvorton (t.e. pasfrazon) kaj / aŭ uzi variaĵon de signoj (majuskloj, minuskloj, ciferoj kaj specialaj signoj).",
|
||||
"certmanager_attempt_to_renew_valid_cert": "La atestilo por la domajno '{domain:s}' ne finiĝos! (Vi eble uzos --force se vi scias kion vi faras)",
|
||||
"restore_running_hooks": "Kurantaj restarigaj hokoj…",
|
||||
"regenconf_pending_applying": "Aplikante pritraktata agordo por kategorio '{category}'…",
|
||||
"service_description_dovecot": "Permesas al retpoŝtaj klientoj aliri / serĉi retpoŝton (per IMAP kaj POP3)",
|
||||
"domain_dns_conf_is_just_a_recommendation": "Ĉi tiu komando montras al vi la *rekomenditan* agordon. Ĝi efektive ne agordas la DNS-agordon por vi. Via respondeco agordi vian DNS-zonon en via registristo laŭ ĉi tiu rekomendo.",
|
||||
"backup_php5_to_php7_migration_may_fail": "Ne povis konverti vian ar archiveivon por subteni PHP 7, vi eble ne povas restarigi viajn PHP-programojn (kialo: {error:s})",
|
||||
"log_backup_restore_system": "Restarigi sistemon de rezerva arkivo",
|
||||
"log_app_change_url": "Ŝanĝu la URL de la apliko '{}'",
|
||||
"service_already_started": "La servo '{service:s}' estas jam komencita",
|
||||
"license_undefined": "nedifinita",
|
||||
"global_settings_setting_security_password_admin_strength": "Admin pasvorta forto",
|
||||
"service_reload_or_restart_failed": "Ne povis reŝargi aŭ rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}",
|
||||
"migrations_list_conflict_pending_done": "Vi ne povas uzi ambaŭ '--previous' kaj '--done' samtempe.",
|
||||
"maindomain_changed": "La ĉefa domajno nun ŝanĝiĝis",
|
||||
"server_shutdown_confirm": "La servilo haltos tuj, ĉu vi certas? [{answers:s}]",
|
||||
"monitor_period_invalid": "Nevalida tempoperiodo",
|
||||
"log_backup_restore_app": "Restarigu '{}' de rezerva ar archiveivo",
|
||||
"log_does_exists": "Ne estas operacio-registro kun la nomo '{log}', uzu 'yunohost loglist' por vidi ĉiujn disponeblajn operaciojn",
|
||||
"service_add_failed": "Ne povis aldoni la servon '{service:s}'",
|
||||
"pattern_password_app": "Bedaŭrinde, pasvortoj ne povas enhavi jenajn signojn: {forbidden_chars}",
|
||||
"this_action_broke_dpkg": "Ĉi tiu ago rompis dpkg / APT (la administrantoj pri la paka sistemo) ... Vi povas provi solvi ĉi tiun problemon per konekto per SSH kaj funkcianta `sudo dpkg --configure -a`.",
|
||||
"log_regen_conf": "Regeneri sistemajn agordojn '{}'",
|
||||
"restore_hook_unavailable": "La restariga skripto por '{part:s}' ne haveblas en via sistemo kaj ankaŭ ne en la ar theivo",
|
||||
"network_check_smtp_ok": "Eksteren retpoŝto (SMTP-haveno 25) ne estas blokita",
|
||||
"log_dyndns_subscribe": "Aboni al YunoHost-subdominio '{}'",
|
||||
"password_too_simple_4": "La pasvorto bezonas almenaŭ 12 signojn kaj enhavas ciferon, majuskle, pli malaltan kaj specialajn signojn",
|
||||
"migration_0003_main_upgrade": "Komencanta ĉefa ĝisdatigo …",
|
||||
"user_info_failed": "Ne povis akiri informojn pri uzanto",
|
||||
"regenconf_file_updated": "Agordodosiero '{conf}' ĝisdatigita",
|
||||
"log_help_to_get_log": "Por vidi la protokolon de la operacio '{desc}', uzu la komandon 'yunohost log display {name}'",
|
||||
"global_settings_setting_security_nginx_compatibility": "Kongruo vs sekureca kompromiso por la TTT-servilo NGINX. Afektas la ĉifradojn (kaj aliajn aspektojn pri sekureco)",
|
||||
"no_internet_connection": "Servilo ne konektita al la interreto",
|
||||
"migration_0008_dsa": "• La DSA-ŝlosilo estos malŝaltita. Tial vi eble bezonos nuligi spuran averton de via SSH-kliento kaj revizii la fingrospuron de via servilo;",
|
||||
"migration_0003_restoring_origin_nginx_conf": "Fileia dosiero /etc/nginx/nginx.conf estis iel redaktita. La migrado reaperos unue al sia originala stato ... La antaŭa dosiero estos havebla kiel {backup_dest}.",
|
||||
"migrate_tsig_end": "Migrado al HMAC-SHA-512 finiĝis",
|
||||
"restore_complete": "Restarigita",
|
||||
"certmanager_couldnt_fetch_intermediate_cert": "Ekvilibrigita kiam vi provis ricevi interajn atestilojn de Let's Encrypt. Atestita instalado / renovigo nuligita - bonvolu reprovi poste.",
|
||||
"hook_exec_failed": "Ne povis funkcii skripto: {path:s}",
|
||||
"global_settings_cant_open_settings": "Ne eblis malfermi agordojn, tial: {reason:s}",
|
||||
"user_created": "Uzanto kreita",
|
||||
"service_description_avahi-daemon": "Permesas al vi atingi vian servilon uzante 'yunohost.local' en via loka reto",
|
||||
"certmanager_attempt_to_replace_valid_cert": "Vi provas anstataŭigi bonan kaj validan atestilon por domajno {domajno:s}! (Uzu --forte pretervidi)",
|
||||
"monitor_stats_period_unavailable": "Ne ekzistas disponeblaj statistikoj por la periodo",
|
||||
"regenconf_updated": "Agordo por kategorio '{category}' ĝisdatigita",
|
||||
"update_apt_cache_warning": "Io iris malbone dum la ĝisdatigo de la kaŝmemoro de APT (paka administranto de Debian). Jen rubujo de la sources.list-linioj, kiuj povus helpi identigi problemajn liniojn:\n{sourcelist}",
|
||||
"regenconf_dry_pending_applying": "Kontrolado de pritraktata agordo, kiu estus aplikita por kategorio '{category}'…",
|
||||
"regenconf_file_copy_failed": "Ne povis kopii la novan agordodosieron '{new}' al '{conf}'",
|
||||
"global_settings_setting_example_string": "Ekzemple korda elekto",
|
||||
"restore_already_installed_app": "App kun la ID '{app:s}' estas jam instalita",
|
||||
"mountpoint_unknown": "Nekonata montpunkto",
|
||||
"log_tools_maindomain": "Faru de '{}' la ĉefa domajno",
|
||||
"maindomain_change_failed": "Ne povis ŝanĝi la ĉefan domajnon",
|
||||
"mail_domain_unknown": "Nevalida retadreso por domajno '{domain:s}'. Bonvolu uzi domajnon administritan de ĉi tiu servilo.",
|
||||
"migrations_cant_reach_migration_file": "Ne povis aliri migrajn dosierojn ĉe la vojo '% s'",
|
||||
"pattern_email": "Devas esti valida retpoŝtadreso (t.e.iu@domain.org)",
|
||||
"mail_alias_remove_failed": "Ne povis forigi retpoŝton alias '{mail:s}'",
|
||||
"regenconf_file_manually_removed": "La dosiero de agordo '{conf}' estis forigita permane, kaj ne estos kreita",
|
||||
"monitor_enabled": "Servilo-monitorado nun",
|
||||
"domain_exists": "La domajno jam ekzistas",
|
||||
"migration_description_0001_change_cert_group_to_sslcert": "Ŝanĝu grupajn permesojn de 'metronomo' al 'ssl-cert'",
|
||||
"mysql_db_creation_failed": "Ne povis krei MySQL-datumbazon",
|
||||
"ldap_initialized": "LDAP inicializis",
|
||||
"migrate_tsig_not_needed": "Vi ne ŝajnas uzi DynDNS-domajnon, do neniu migrado necesas.",
|
||||
"certmanager_domain_cert_not_selfsigned": "La atestilo por domajno {domajno:s} ne estas mem-subskribita. Ĉu vi certas, ke vi volas anstataŭigi ĝin? (Uzu '--force' por fari tion.)",
|
||||
"certmanager_unable_to_parse_self_CA_name": "Ne povis trapasi nomon de mem-subskribinta aŭtoritato (dosiero: {file: s})",
|
||||
"log_selfsigned_cert_install": "Instalu mem-subskribitan atestilon sur '{}' domajno",
|
||||
"log_tools_reboot": "Reklamu vian servilon",
|
||||
"certmanager_cert_install_success": "Ni Ĉifru atestilon nun instalitan por la domajno '{domain:s}'",
|
||||
"global_settings_bad_choice_for_enum": "Malbona elekto por agordo {setting:s}, ricevita '{choice:s}', sed disponeblaj elektoj estas: {available_choices:s}",
|
||||
"server_shutdown": "La servilo haltos",
|
||||
"log_tools_migrations_migrate_forward": "Migri antaŭen",
|
||||
"migration_0008_no_warning": "Supersalti vian SSH-agordon estu sekura, kvankam ĉi tio ne povas esti promesita! Ekfunkciu la migradon por superregi ĝin. Alie, vi ankaŭ povas salti la migradon, kvankam ĝi ne rekomendas.",
|
||||
"regenconf_now_managed_by_yunohost": "La agorda dosiero '{conf}' nun estas administrata de YunoHost (kategorio {category}).",
|
||||
"server_reboot_confirm": "Ĉu la servilo rekomencos tuj, ĉu vi certas? [{answers:s}]",
|
||||
"log_app_install": "Instalu la aplikon '{}'",
|
||||
"service_description_dnsmasq": "Traktas rezolucion de domajna nomo (DNS)",
|
||||
"global_settings_unknown_type": "Neatendita situacio, la agordo {setting:s} ŝajnas havi la tipon {unknown_type:s} sed ĝi ne estas tipo subtenata de la sistemo.",
|
||||
"migration_0003_problematic_apps_warning": "Bonvolu noti, ke la sekvaj eventuale problemaj instalitaj apps estis detektitaj. Ĝi aspektas, ke tiuj ne estis instalitaj de aparato aŭ ne estas markitaj kiel \"funkciantaj\". Tial ne eblas garantii, ke ili ankoraŭ funkcios post la ĝisdatigo: {problematic_apps}",
|
||||
"domain_hostname_failed": "Ne povis agordi novan gastigilon. Ĉi tio eble kaŭzos problemon poste (eble bone).",
|
||||
"server_reboot": "La servilo rekomenciĝos",
|
||||
"regenconf_failed": "Ne povis regeneri la agordon por kategorio(j): {categories}",
|
||||
"domain_uninstall_app_first": "Unu aŭ pluraj programoj estas instalitaj en ĉi tiu domajno. Bonvolu malinstali ilin antaŭ ol daŭrigi la domajnan forigon",
|
||||
"port_unavailable": "Haveno {port:d} ne haveblas",
|
||||
"service_unknown": "Nekonata servo '{service:s}'",
|
||||
"migration_0003_start": "Komencante migradon al Stretch. La protokoloj haveblos en {logfile}.",
|
||||
"monitor_stats_no_update": "Neniuj monitoradaj statistikoj ĝisdatigi",
|
||||
"domain_deletion_failed": "Ne povis forigi domajnon {domain}: {error}",
|
||||
"log_user_update": "Ĝisdatigu uzantinformojn de '{}'",
|
||||
"user_creation_failed": "Ne povis krei uzanton {user}: {error}",
|
||||
"migrations_migration_has_failed": "Migrado {id} ne kompletigis, abolis. Eraro: {exception}",
|
||||
"done": "Farita",
|
||||
"log_domain_remove": "Forigi domon '{}' de agordo de sistemo",
|
||||
"monitor_not_enabled": "Servila monitorado estas malŝaltita",
|
||||
"diagnosis_debian_version_error": "Ne povis retrovi la Debianan version: {error}",
|
||||
"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: s}'",
|
||||
"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:s} ne povas provizi domajnon {domain:s}.",
|
||||
"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",
|
||||
"executing_script": "Plenumanta skripto '{script:s}' …",
|
||||
"service_cmd_exec_failed": "Ne povis plenumi la komandon '{command:s}'",
|
||||
"migration_0007_cancelled": "Ne povis plibonigi la manieron kiel via SSH-agordo estas administrita.",
|
||||
"migrate_tsig_failed": "Ne povis migri la DynDNS-domajnon '{domain}' al HMAC-SHA-512, ruliĝante. Eraro: {error_code}, {error}",
|
||||
"pattern_lastname": "Devas esti valida familinomo",
|
||||
"service_enabled": "'{service:s}' servo malŝaltita",
|
||||
"certmanager_no_cert_file": "Ne povis legi la atestan dosieron por la domajno {domain:s} (dosiero: {file:s})",
|
||||
"migration_0008_port": "• Vi devos konekti uzante la havenon 22 anstataŭ via nuna kutimo SSH-haveno. Sentu vin libera reconfiguri ĝin;",
|
||||
"domain_creation_failed": "Ne povis krei domajnon {domain}: {error}",
|
||||
"certmanager_domain_http_not_working": "Ŝajnas ke la domajno {domain:s} ne atingeblas per HTTP. Kontrolu, ke via DNS kaj NGINX-agordo ĝustas",
|
||||
"domain_cannot_remove_main": "Ne eblas forigi ĉefan domajnon. Fiksu unu unue",
|
||||
"service_reloaded_or_restarted": "'{service:s}' servo reŝarĝis aŭ rekomencis",
|
||||
"mysql_db_initialized": "La datumbazo MySQL jam estas pravalorizita",
|
||||
"log_domain_add": "Aldonu '{}' domajnon en sisteman agordon",
|
||||
"global_settings_bad_type_for_setting": "Malbona tipo por agordo {setting:s}, ricevita {received_type:s}, atendata {expected_type:s}",
|
||||
"unlimit": "Neniu kvoto",
|
||||
"dyndns_cron_installed": "Kreita laboro DynDNS cron",
|
||||
"system_username_exists": "Uzantnomo jam ekzistas en la listo de uzantoj de sistemo",
|
||||
"firewall_reloaded": "Fajroŝirmilo reŝarĝis",
|
||||
"service_restarted": "'{service:s}' servo rekomencis",
|
||||
"pattern_username": "Devas esti minuskulaj literoj kaj minuskloj nur",
|
||||
"extracting": "Eltirante…",
|
||||
"restore_app_failed": "Ne povis restarigi la programon '{app:s}'",
|
||||
"yunohost_configured": "YunoHost nun agordis",
|
||||
"certmanager_self_ca_conf_file_not_found": "Ne povis trovi agorddosieron por mem-subskriba aŭtoritato (dosiero: {file:s})",
|
||||
"log_app_remove": "Forigu la aplikon '{}'",
|
||||
"service_restart_failed": "Ne povis rekomenci la servon '{service:s}'\n\nLastatempaj servaj protokoloj: {logs:s}",
|
||||
"firewall_rules_cmd_failed": "Iuj komandoj pri fajroŝirmilo malsukcesis. Pliaj informoj en ensaluto.",
|
||||
"certmanager_certificate_fetching_or_enabling_failed": "Provante uzi la novan atestilon por {domain:s} ne funkciis …",
|
||||
"app_full_domain_unavailable": "Bedaŭrinde, ĉi tiu app devas esti instalita sur propra domajno, sed aliaj programoj jam estas instalitaj sur la domajno '{domain}'. Vi povus uzi subdominon dediĉitan al ĉi tiu app anstataŭe.",
|
||||
"migration_0011_slapd_config_will_be_overwritten": "Ŝajnas ke vi permane redaktis la slapd-agordon. Por ĉi tiu kritika migrado, YunoHost bezonas devigi la ĝisdatigon de la slapd-agordo. La originalaj dosieroj estos rezervitaj en {conf_backup_folder}.",
|
||||
"group_cannot_edit_all_users": "La grupo 'all_users' ne povas esti redaktita permane. Ĝi estas speciala grupo celita enhavi ĉiujn uzantojn registritajn en YunoHost",
|
||||
"group_cannot_edit_visitors": "La grupo 'vizitantoj' ne povas esti redaktita permane. Ĝi estas speciala grupo reprezentanta anonimajn vizitantojn",
|
||||
"group_cannot_edit_primary_group": "La grupo '{group}' ne povas esti redaktita permane. Ĝi estas la primara grupo celita enhavi nur unu specifan uzanton.",
|
||||
"log_permission_url": "Ĝisdatigu url-rilataj al permeso '{}'",
|
||||
"permission_already_up_to_date": "La permeso ne estis ĝisdatigita ĉar la petoj pri aldono/forigo jam kongruas kun la aktuala stato.",
|
||||
"permission_currently_allowed_for_visitors": "Ĉi tiu permeso estas nuntempe donita al vizitantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson de \"vizitantoj\" aŭ forigi la aliajn grupojn al kiuj ĝi nun estas koncedita.",
|
||||
"permission_currently_allowed_for_all_users": "Ĉi tiu permeso estas nuntempe donita al ĉiuj uzantoj aldone al aliaj grupoj. Vi probable volas aŭ forigi la permeson \"all_users\" aŭ forigi la aliajn grupojn, kiujn ĝi nuntempe donas.",
|
||||
"app_install_failed": "Ne povis instali {app} : {error}",
|
||||
"app_install_script_failed": "Eraro okazis en la skripto de instalado de la app",
|
||||
"app_remove_after_failed_install": "Forigado de la app post la instala fiasko …"
|
||||
}
|
||||
|
|
725
locales/es.json
725
locales/es.json
|
@ -2,37 +2,37 @@
|
|||
"action_invalid": "Acción no válida '{action:s} 1'",
|
||||
"admin_password": "Contraseña administrativa",
|
||||
"admin_password_change_failed": "No se puede cambiar la contraseña",
|
||||
"admin_password_changed": "La contraseña administrativa ha sido cambiada",
|
||||
"app_already_installed": "{app:s} 2 ya está instalada",
|
||||
"app_argument_choice_invalid": "Opción no válida para el argumento '{name:s} 3', deber una de {choices:s} 4",
|
||||
"app_argument_invalid": "Valor no válido para el argumento '{name:s} 5': {error:s} 6",
|
||||
"admin_password_changed": "La contraseña de administración ha sido cambiada",
|
||||
"app_already_installed": "{app:s} ya está instalada",
|
||||
"app_argument_choice_invalid": "Use una de estas opciones «{choices:s}» para el argumento «{name:s}»",
|
||||
"app_argument_invalid": "Elija un valor válido para el argumento «{name:s}»: {error:s}",
|
||||
"app_argument_required": "Se requiere el argumento '{name:s} 7'",
|
||||
"app_extraction_failed": "No se pudieron extraer los archivos de instalación",
|
||||
"app_id_invalid": "Id de la aplicación no válida",
|
||||
"app_id_invalid": "ID de la aplicación no válida",
|
||||
"app_incompatible": "La aplicación {app} no es compatible con su versión de YunoHost",
|
||||
"app_install_files_invalid": "Los archivos de instalación no son válidos",
|
||||
"app_location_already_used": "La aplicación {app} ya está instalada en esta localización ({path})",
|
||||
"app_location_install_failed": "No se puede instalar la aplicación en esta localización porque entra en conflicto con la aplicación '{other_app}' ya instalada en '{other_path}'",
|
||||
"app_manifest_invalid": "El manifiesto de la aplicación no es válido: {error}",
|
||||
"app_no_upgrade": "No hay aplicaciones para actualizar",
|
||||
"app_install_files_invalid": "Estos archivos no se pueden instalar",
|
||||
"app_location_already_used": "La aplicación «{app}» ya está instalada en ({path})",
|
||||
"app_location_install_failed": "No se puede instalar la aplicación ahí porque entra en conflicto con la aplicación «{other_app}» ya instalada en «{other_path}»",
|
||||
"app_manifest_invalid": "Algo va mal con el manifiesto de la aplicación: {error}",
|
||||
"app_no_upgrade": "Todas las aplicaciones están ya actualizadas",
|
||||
"app_not_correctly_installed": "La aplicación {app:s} 8 parece estar incorrectamente instalada",
|
||||
"app_not_installed": "{app:s} 9 no está instalada",
|
||||
"app_not_installed": "No se pudo encontrar la aplicación «{app:s}» en la lista de aplicaciones instaladas: {all_apps}",
|
||||
"app_not_properly_removed": "La {app:s} 0 no ha sido desinstalada correctamente",
|
||||
"app_package_need_update": "El paquete de la aplicación {app} necesita ser actualizada debido a los cambios en YunoHost",
|
||||
"app_recent_version_required": "{:s} requiere una versión más reciente de moulinette ",
|
||||
"app_removed": "{app:s} ha sido eliminada",
|
||||
"app_requirements_checking": "Comprobando los paquetes requeridos por {app}...",
|
||||
"app_requirements_failed": "No se cumplen los requisitos para {app}: {error}",
|
||||
"app_removed": "Eliminado {app:s}",
|
||||
"app_requirements_checking": "Comprobando los paquetes necesarios para {app}…",
|
||||
"app_requirements_failed": "No se cumplen algunos requisitos para {app}: {error}",
|
||||
"app_requirements_unmeet": "No se cumplen los requisitos para {app}, el paquete {pkgname} ({version}) debe ser {spec}",
|
||||
"app_sources_fetch_failed": "No se pudieron descargar los archivos del código fuente",
|
||||
"app_sources_fetch_failed": "No se pudieron obtener los archivos con el código fuente, ¿es el URL correcto?",
|
||||
"app_unknown": "Aplicación desconocida",
|
||||
"app_unsupported_remote_type": "Tipo remoto no soportado por la aplicación",
|
||||
"app_upgrade_failed": "No se pudo actualizar la aplicación {app:s}",
|
||||
"app_upgraded": "{app:s} ha sido actualizada",
|
||||
"appslist_fetched": "La lista de aplicaciones {appslist:s} ha sido descargada",
|
||||
"appslist_removed": "La lista de aplicaciones {appslist:s} ha sido eliminada",
|
||||
"appslist_retrieve_error": "No se pudo recuperar la lista remota de aplicaciones {appslist:s} : {error:s}",
|
||||
"appslist_unknown": "Lista de aplicaciones {appslist:s} desconocida.",
|
||||
"app_upgrade_failed": "No se pudo actualizar {app:s}",
|
||||
"app_upgraded": "Actualizado {app:s}",
|
||||
"appslist_fetched": "Lista de aplicaciones «{appslist:s}» actualizada",
|
||||
"appslist_removed": "Eliminada la lista de aplicaciones «{appslist:s}»",
|
||||
"appslist_retrieve_error": "No se puede recuperar la lista remota de aplicaciones «{appslist:s}»: {error:s}",
|
||||
"appslist_unknown": "Lista de aplicaciones «{appslist:s}» desconocida.",
|
||||
"ask_current_admin_password": "Contraseña administrativa actual",
|
||||
"ask_email": "Dirección de correo electrónico",
|
||||
"ask_firstname": "Nombre",
|
||||
|
@ -42,43 +42,43 @@
|
|||
"ask_new_admin_password": "Nueva contraseña administrativa",
|
||||
"ask_password": "Contraseña",
|
||||
"backup_action_required": "Debe especificar algo que guardar",
|
||||
"backup_app_failed": "No es posible realizar la copia de seguridad de la aplicación '{app:s}'",
|
||||
"backup_archive_app_not_found": "La aplicación '{app:s}' no ha sido encontrada en la copia de seguridad",
|
||||
"backup_app_failed": "No se pudo respaldar la aplicación «{app:s}»",
|
||||
"backup_archive_app_not_found": "No se pudo encontrar la aplicación «{app:s}» en el archivo de respaldo",
|
||||
"backup_archive_hook_not_exec": "El hook {hook:s} no ha sido ejecutado en esta copia de seguridad",
|
||||
"backup_archive_name_exists": "Ya existe una copia de seguridad con ese nombre",
|
||||
"backup_archive_name_exists": "Ya existe un archivo de respaldo con este nombre.",
|
||||
"backup_archive_name_unknown": "Copia de seguridad local desconocida '{name:s}'",
|
||||
"backup_archive_open_failed": "No se pudo abrir la copia de seguridad",
|
||||
"backup_cleaning_failed": "No se puede limpiar el directorio temporal de copias de seguridad",
|
||||
"backup_archive_open_failed": "No se pudo abrir el archivo de respaldo",
|
||||
"backup_cleaning_failed": "No se pudo limpiar la carpeta de respaldo temporal",
|
||||
"backup_created": "Se ha creado la copia de seguridad",
|
||||
"backup_creating_archive": "Creando copia de seguridad...",
|
||||
"backup_creation_failed": "No se pudo crear la copia de seguridad",
|
||||
"backup_delete_error": "No se puede eliminar '{path:s}'",
|
||||
"backup_deleted": "La copia de seguridad ha sido eliminada",
|
||||
"backup_extracting_archive": "Extrayendo la copia de seguridad...",
|
||||
"backup_hook_unknown": "Hook de copia de seguridad desconocido '{hook:s}'",
|
||||
"backup_invalid_archive": "La copia de seguridad no es válida",
|
||||
"backup_nothings_done": "No hay nada que guardar",
|
||||
"backup_output_directory_forbidden": "Directorio de salida no permitido. Las copias de seguridad no pueden ser creadas en /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var o en los subdirectorios de /home/yunohost.backup/archives",
|
||||
"backup_output_directory_not_empty": "El directorio de salida no está vacío",
|
||||
"backup_creating_archive": "Creando el archivo de copia de seguridad…",
|
||||
"backup_creation_failed": "No se pudo crear el archivo de respaldo",
|
||||
"backup_delete_error": "No se pudo eliminar «{path:s}»",
|
||||
"backup_deleted": "Eliminada la copia de seguridad",
|
||||
"backup_extracting_archive": "Extrayendo el archivo de respaldo…",
|
||||
"backup_hook_unknown": "El gancho «{hook:s}» de la copia de seguridad es desconocido",
|
||||
"backup_invalid_archive": "Esto no es un archivo de respaldo",
|
||||
"backup_nothings_done": "Nada que guardar",
|
||||
"backup_output_directory_forbidden": "Elija un directorio de salida diferente. No se pueden crear copias de seguridad en las subcarpetas de /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var o /home/yunohost.backup/archives",
|
||||
"backup_output_directory_not_empty": "Debe elegir un directorio de salida vacío",
|
||||
"backup_output_directory_required": "Debe proporcionar un directorio de salida para la copia de seguridad",
|
||||
"backup_running_app_script": "Ejecutando la script de copia de seguridad de la aplicación '{app:s}'...",
|
||||
"backup_running_hooks": "Ejecutando los hooks de copia de seguridad...",
|
||||
"custom_app_url_required": "Debe proporcionar una URL para actualizar su aplicación personalizada {app:s}",
|
||||
"custom_appslist_name_required": "Debe proporcionar un nombre para su lista de aplicaciones personalizadas",
|
||||
"diagnosis_debian_version_error": "No se puede obtener la versión de Debian: {error}",
|
||||
"diagnosis_kernel_version_error": "No se puede obtener la versión del kernel: {error}",
|
||||
"diagnosis_monitor_disk_error": "No se pueden monitorizar los discos: {error}",
|
||||
"diagnosis_monitor_network_error": "No se puede monitorizar la red: {error}",
|
||||
"diagnosis_monitor_system_error": "No se puede monitorizar el sistema: {error}",
|
||||
"diagnosis_no_apps": "Aplicación no instalada",
|
||||
"dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecute 'apt-get remove bind9 && apt-get install dnsmasq'",
|
||||
"domain_cert_gen_failed": "No se pudo crear el certificado",
|
||||
"domain_created": "El dominio ha sido creado",
|
||||
"domain_creation_failed": "No se pudo crear el dominio",
|
||||
"domain_deleted": "El dominio ha sido eliminado",
|
||||
"domain_deletion_failed": "No se pudo borrar el dominio",
|
||||
"domain_dyndns_already_subscribed": "Ya está suscrito a un dominio DynDNS",
|
||||
"domain_dyndns_invalid": "Dominio no válido para usar con DynDNS",
|
||||
"diagnosis_debian_version_error": "No se pudo obtener la versión de Debian: {error}",
|
||||
"diagnosis_kernel_version_error": "No se pudo obtener la versión del núcleo: {error}",
|
||||
"diagnosis_monitor_disk_error": "No se pudieron monitorizar los discos: {error}",
|
||||
"diagnosis_monitor_network_error": "No se pudo monitorizar la red: {error}",
|
||||
"diagnosis_monitor_system_error": "No se pudo monitorizar el sistema: {error}",
|
||||
"diagnosis_no_apps": "No hay tal aplicación instalada",
|
||||
"dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecute «apt-get remove bind9 && apt-get install it»",
|
||||
"domain_cert_gen_failed": "No se pudo generar el certificado",
|
||||
"domain_created": "Dominio creado",
|
||||
"domain_creation_failed": "No se pudo crear el dominio {domain}: {error}",
|
||||
"domain_deleted": "Dominio eliminado",
|
||||
"domain_deletion_failed": "No se pudo eliminar el dominio {domain}: {error}",
|
||||
"domain_dyndns_already_subscribed": "Ya se ha suscrito a un dominio de DynDNS",
|
||||
"domain_dyndns_invalid": "Este dominio no se puede usar con DynDNS",
|
||||
"domain_dyndns_root_unknown": "Dominio raíz de DynDNS desconocido",
|
||||
"domain_exists": "El dominio ya existe",
|
||||
"domain_uninstall_app_first": "Una o más aplicaciones están instaladas en este dominio. Debe desinstalarlas antes de eliminar el dominio",
|
||||
|
@ -86,78 +86,78 @@
|
|||
"domain_zone_exists": "El archivo de zona del DNS ya existe",
|
||||
"domain_zone_not_found": "No se ha encontrado el archivo de zona del DNS para el dominio [:s]",
|
||||
"done": "Hecho.",
|
||||
"downloading": "Descargando...",
|
||||
"dyndns_cron_installed": "La tarea cron para DynDNS ha sido instalada",
|
||||
"dyndns_cron_remove_failed": "No se pudo eliminar la tarea cron DynDNS",
|
||||
"dyndns_cron_removed": "La tarea cron DynDNS ha sido eliminada",
|
||||
"dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en el DynDNS",
|
||||
"dyndns_ip_updated": "Su dirección IP ha sido actualizada en el DynDNS",
|
||||
"dyndns_key_generating": "Se está generando la clave del DNS. Esto podría tardar unos minutos...",
|
||||
"downloading": "Descargando…",
|
||||
"dyndns_cron_installed": "Creado el trabajo de cron de DynDNS",
|
||||
"dyndns_cron_remove_failed": "No se pudo eliminar el trabajo de cron de DynDNS por: {error}",
|
||||
"dyndns_cron_removed": "Eliminado el trabajo de cron de DynDNS",
|
||||
"dyndns_ip_update_failed": "No se pudo actualizar la dirección IP en DynDNS",
|
||||
"dyndns_ip_updated": "Actualizada su IP en DynDNS",
|
||||
"dyndns_key_generating": "Generando la clave del DNS. Esto podría tardar un rato.",
|
||||
"dyndns_key_not_found": "No se ha encontrado la clave DNS para el dominio",
|
||||
"dyndns_no_domain_registered": "Ningún dominio ha sido registrado con DynDNS",
|
||||
"dyndns_registered": "El dominio DynDNS ha sido registrado",
|
||||
"dyndns_registration_failed": "No se pudo registrar el dominio DynDNS: {error:s}",
|
||||
"dyndns_unavailable": "El dominio {domain:s} no está disponible.",
|
||||
"executing_command": "Ejecutando el comando '{command:s}'...",
|
||||
"executing_script": "Ejecutando el script '{script:s}'...",
|
||||
"extracting": "Extrayendo...",
|
||||
"dyndns_no_domain_registered": "Ningún dominio registrado con DynDNS",
|
||||
"dyndns_registered": "Registrado dominio de DynDNS",
|
||||
"dyndns_registration_failed": "No se pudo registrar el dominio de DynDNS: {error:s}",
|
||||
"dyndns_unavailable": "El dominio «{domain:s}» no está disponible.",
|
||||
"executing_command": "Ejecutando la orden «{command:s}»…",
|
||||
"executing_script": "Ejecutando el guión «{script:s}»…",
|
||||
"extracting": "Extrayendo…",
|
||||
"field_invalid": "Campo no válido '{:s}'",
|
||||
"firewall_reload_failed": "No se pudo recargar el cortafuegos",
|
||||
"firewall_reloaded": "El cortafuegos ha sido recargado",
|
||||
"firewall_rules_cmd_failed": "No se pudieron aplicar algunas reglas del cortafuegos. Para más información consulte el registro.",
|
||||
"firewall_reloaded": "Cortafuegos recargado",
|
||||
"firewall_rules_cmd_failed": "Algunas órdenes de las reglas del cortafuegos han fallado. Más información en el registro.",
|
||||
"format_datetime_short": "%d/%m/%Y %I:%M %p",
|
||||
"hook_argument_missing": "Falta un parámetro '{:s}'",
|
||||
"hook_choice_invalid": "Selección inválida '{:s}'",
|
||||
"hook_exec_failed": "No se puede ejecutar el script: {path:s}",
|
||||
"hook_exec_not_terminated": "La ejecución del script no ha terminado: {path:s}",
|
||||
"hook_list_by_invalid": "Enumerar los hooks por validez",
|
||||
"hook_exec_failed": "No se pudo ejecutar el guión: {path:s}",
|
||||
"hook_exec_not_terminated": "El guión no terminó correctamente:{path:s}",
|
||||
"hook_list_by_invalid": "Esta propiedad no se puede usar para enumerar ganchos («hooks»)",
|
||||
"hook_name_unknown": "Nombre de hook desconocido '{name:s}'",
|
||||
"installation_complete": "Instalación finalizada",
|
||||
"installation_failed": "No se pudo realizar la instalación",
|
||||
"installation_failed": "Algo ha ido mal con la instalación",
|
||||
"ip6tables_unavailable": "No puede modificar ip6tables aquí. O bien está en un 'container' o su kernel no soporta esta opción",
|
||||
"iptables_unavailable": "No puede modificar iptables aquí. O bien está en un 'container' o su kernel no soporta esta opción",
|
||||
"ldap_initialized": "Se ha inicializado LDAP",
|
||||
"ldap_initialized": "Inicializado LDAP",
|
||||
"license_undefined": "indefinido",
|
||||
"mail_alias_remove_failed": "No se pudo eliminar el alias de correo '{mail:s}'",
|
||||
"mail_domain_unknown": "El dominio de correo '{domain:s}' es desconocido",
|
||||
"mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo '{mail:s}'",
|
||||
"maindomain_change_failed": "No se pudo cambiar el dominio principal",
|
||||
"maindomain_changed": "Se ha cambiado el dominio principal",
|
||||
"monitor_disabled": "La monitorización del sistema ha sido deshabilitada",
|
||||
"monitor_enabled": "La monitorización del sistema ha sido habilitada",
|
||||
"monitor_glances_con_failed": "No se pudo conectar al servidor Glances",
|
||||
"monitor_not_enabled": "La monitorización del sistema no está habilitada",
|
||||
"mail_alias_remove_failed": "No se pudo eliminar el alias de correo «{mail:s}»",
|
||||
"mail_domain_unknown": "Dirección de correo no válida para el dominio «{domain:s}». Use un dominio administrado por este servidor.",
|
||||
"mail_forward_remove_failed": "No se pudo eliminar el reenvío de correo «{mail:s}»",
|
||||
"main_domain_change_failed": "No se pudo cambiar el dominio principal",
|
||||
"main_domain_changed": "El dominio principal ha cambiado",
|
||||
"monitor_disabled": "La monitorización del servidor está ahora desactivada",
|
||||
"monitor_enabled": "La monitorización del servidor está ahora activada",
|
||||
"monitor_glances_con_failed": "No se pudo conectar al servidor de Glances",
|
||||
"monitor_not_enabled": "La monitorización del servidor está apagada",
|
||||
"monitor_period_invalid": "Período de tiempo no válido",
|
||||
"monitor_stats_file_not_found": "No se pudo encontrar el archivo de estadísticas",
|
||||
"monitor_stats_no_update": "No hay estadísticas de monitorización para actualizar",
|
||||
"monitor_stats_period_unavailable": "No hay estadísticas para el período",
|
||||
"mountpoint_unknown": "Punto de montaje desconocido",
|
||||
"mysql_db_creation_failed": "No se pudo crear la base de datos MySQL",
|
||||
"mysql_db_init_failed": "No se pudo iniciar la base de datos MySQL",
|
||||
"mysql_db_initialized": "La base de datos MySQL ha sido inicializada",
|
||||
"mysql_db_init_failed": "No se pudo inicializar la base de datos MySQL",
|
||||
"mysql_db_initialized": "La base de datos MySQL está ahora inicializada",
|
||||
"network_check_mx_ko": "El registro DNS MX no está configurado",
|
||||
"network_check_smtp_ko": "El puerto 25 (SMTP) para el correo saliente parece estar bloqueado por su red",
|
||||
"network_check_smtp_ok": "El puerto de salida del correo electrónico (25, SMTP) no está bloqueado",
|
||||
"network_check_smtp_ko": "El correo saliente (SMTP puerto 25) parece estar bloqueado por su red",
|
||||
"network_check_smtp_ok": "El correo saliente (SMTP puerto 25) no está bloqueado",
|
||||
"new_domain_required": "Debe proporcionar el nuevo dominio principal",
|
||||
"no_appslist_found": "No se ha encontrado ninguna lista de aplicaciones",
|
||||
"no_internet_connection": "El servidor no está conectado a Internet",
|
||||
"no_ipv6_connectivity": "La conexión por IPv6 no está disponible",
|
||||
"no_restore_script": "No se ha encontrado un script de restauración para la aplicación '{app:s}'",
|
||||
"not_enough_disk_space": "No hay suficiente espacio en '{path:s}'",
|
||||
"package_not_installed": "El paquete '{pkgname}' no está instalado",
|
||||
"not_enough_disk_space": "No hay espacio libre suficiente en «{path:s}»",
|
||||
"package_not_installed": "El paquete «{pkgname}» no está instalado",
|
||||
"package_unexpected_error": "Ha ocurrido un error inesperado procesando el paquete '{pkgname}'",
|
||||
"package_unknown": "Paquete desconocido '{pkgname}'",
|
||||
"packages_no_upgrade": "No hay paquetes para actualizar",
|
||||
"packages_upgrade_critical_later": "Los paquetes críticos ({packages:s}) serán actualizados más tarde",
|
||||
"packages_upgrade_failed": "No se pudieron actualizar todos los paquetes",
|
||||
"path_removal_failed": "No se pudo eliminar la ruta {:s}",
|
||||
"pattern_backup_archive_name": "Debe ser un nombre de archivo válido con un máximo de 30 caracteres, solo se admiten caracteres alfanuméricos, los guiones -_ y el punto",
|
||||
"pattern_backup_archive_name": "Debe ser un nombre de archivo válido con un máximo de 30 caracteres, solo se admiten caracteres alfanuméricos y los caracteres -_. (guiones y punto)",
|
||||
"pattern_domain": "El nombre de dominio debe ser válido (por ejemplo mi-dominio.org)",
|
||||
"pattern_email": "Debe ser una dirección de correo electrónico válida (por ejemplo, alguien@dominio.org)",
|
||||
"pattern_email": "Debe ser una dirección de correo electrónico válida (p.ej. alguien@example.com)",
|
||||
"pattern_firstname": "Debe ser un nombre válido",
|
||||
"pattern_lastname": "Debe ser un apellido válido",
|
||||
"pattern_listname": "Solo se pueden usar caracteres alfanuméricos y el guion bajo",
|
||||
"pattern_mailbox_quota": "El tamaño de cuota debe tener uno de los sufijos b/k/M/G/T. Usar 0 para cuota ilimitada",
|
||||
"pattern_mailbox_quota": "Debe ser un tamaño con el sufijo «b/k/M/G/T» o «0» para no tener una cuota",
|
||||
"pattern_password": "Debe contener al menos 3 caracteres",
|
||||
"pattern_port": "Debe ser un número de puerto válido (es decir, entre 0-65535)",
|
||||
"pattern_port_or_range": "Debe ser un número de puerto válido (es decir entre 0-65535) o un intervalo de puertos (por ejemplo 100:200)",
|
||||
|
@ -167,22 +167,22 @@
|
|||
"port_already_opened": "El puerto {port:d} ya está abierto para las conexiones {ip_version:s}",
|
||||
"port_available": "El puerto {port:d} está disponible",
|
||||
"port_unavailable": "El puerto {port:d} no está disponible",
|
||||
"restore_action_required": "Debe especificar algo que restaurar",
|
||||
"restore_already_installed_app": "Una aplicación con la id '{app:s}' ya está instalada",
|
||||
"restore_app_failed": "No se puede restaurar la aplicación '{app:s}'",
|
||||
"restore_cleaning_failed": "No se puede limpiar el directorio temporal de restauración",
|
||||
"restore_complete": "Restauración finalizada",
|
||||
"restore_action_required": "Debe escoger algo que restaurar",
|
||||
"restore_already_installed_app": "Una aplicación con el ID «{app:s}» ya está instalada",
|
||||
"restore_app_failed": "No se pudo restaurar la aplicación «{app:s}»",
|
||||
"restore_cleaning_failed": "No se pudo limpiar el directorio temporal de restauración",
|
||||
"restore_complete": "Restaurada",
|
||||
"restore_confirm_yunohost_installed": "¿Realmente desea restaurar un sistema ya instalado? [{answers:s}]",
|
||||
"restore_failed": "No se pudo restaurar el sistema",
|
||||
"restore_hook_unavailable": "El script de restauración '{part:s}' no está disponible en su sistema y tampoco en el archivo",
|
||||
"restore_hook_unavailable": "El guión de restauración para «{part:s}» no está disponible en su sistema y tampoco en el archivo",
|
||||
"restore_nothings_done": "No se ha restaurado nada",
|
||||
"restore_running_app_script": "Ejecutando el script de restauración de la aplicación '{app:s}'...",
|
||||
"restore_running_hooks": "Ejecutando los hooks de restauración...",
|
||||
"service_add_failed": "No se pudo añadir el servicio '{service:s}'",
|
||||
"service_added": "Servicio '{service:s}' ha sido añadido",
|
||||
"service_already_started": "El servicio '{service:s}' ya ha sido inicializado",
|
||||
"service_already_stopped": "El servicio '{service:s}' ya ha sido detenido",
|
||||
"service_cmd_exec_failed": "No se pudo ejecutar el comando '{command:s}'",
|
||||
"restore_running_app_script": "Restaurando la aplicación «{app:s}»…",
|
||||
"restore_running_hooks": "Ejecutando los ganchos de restauración…",
|
||||
"service_add_failed": "No se pudo añadir el servicio «{service:s}»",
|
||||
"service_added": "Añadido el servicio «{service:s}»",
|
||||
"service_already_started": "El servicio «{service:s}» ya está funcionando",
|
||||
"service_already_stopped": "El servicio «{service:s}» ya ha sido detenido",
|
||||
"service_cmd_exec_failed": "No se pudo ejecutar la orden «{command:s}»",
|
||||
"service_conf_file_backed_up": "Se ha realizado una copia de seguridad del archivo de configuración '{conf}' en '{backup}'",
|
||||
"service_conf_file_copy_failed": "No se puede copiar el nuevo archivo de configuración '{new}' a {conf}",
|
||||
"service_conf_file_manually_modified": "El archivo de configuración '{conf}' ha sido modificado manualmente y no será actualizado",
|
||||
|
@ -194,134 +194,449 @@
|
|||
"service_conf_up_to_date": "La configuración del servicio '{service}' ya está actualizada",
|
||||
"service_conf_updated": "La configuración ha sido actualizada para el servicio '{service}'",
|
||||
"service_conf_would_be_updated": "La configuración podría haber sido actualizada para el servicio '{service} 1'",
|
||||
"service_disable_failed": "No se pudo deshabilitar el servicio '{service:s}'",
|
||||
"service_disabled": "El servicio '{service:s}' ha sido deshabilitado",
|
||||
"service_enable_failed": "No se pudo habilitar el servicio '{service:s}'",
|
||||
"service_enabled": "El servicio '{service:s}' ha sido habilitado",
|
||||
"service_no_log": "No hay ningún registro para el servicio '{service:s}'",
|
||||
"service_disable_failed": "No se pudo desactivar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}",
|
||||
"service_disabled": "El servicio «{service:s}» ha sido desactivado",
|
||||
"service_enable_failed": "No se pudo activar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}",
|
||||
"service_enabled": "El servicio «{service:s}» ha sido desactivado",
|
||||
"service_no_log": "No hay ningún registro que mostrar para el servicio «{service:s}»",
|
||||
"service_regenconf_dry_pending_applying": "Comprobando configuración pendiente que podría haber sido aplicada al servicio '{service}'...",
|
||||
"service_regenconf_failed": "No se puede regenerar la configuración para el servicio(s): {services}",
|
||||
"service_regenconf_pending_applying": "Aplicando la configuración pendiente para el servicio '{service}'...",
|
||||
"service_remove_failed": "No se pudo desinstalar el servicio '{service:s}'",
|
||||
"service_removed": "El servicio '{service:s}' ha sido desinstalado",
|
||||
"service_start_failed": "No se pudo iniciar el servicio '{service:s}'\n\nRegistros de servicio recientes : {logs:s}",
|
||||
"service_started": "El servicio '{service:s}' ha sido iniciado",
|
||||
"service_status_failed": "No se pudo determinar el estado del servicio '{service:s}'",
|
||||
"service_stop_failed": "No se pudo detener el servicio '{service:s}'",
|
||||
"service_stopped": "El servicio '{service:s}' ha sido detenido",
|
||||
"service_remove_failed": "No se pudo eliminar el servicio «{service:s}»",
|
||||
"service_removed": "Eliminado el servicio «{service:s}»",
|
||||
"service_start_failed": "No se pudo iniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}",
|
||||
"service_started": "Iniciado el servicio «{service:s}»",
|
||||
"service_status_failed": "No se pudo determinar el estado del servicio «{service:s}»",
|
||||
"service_stop_failed": "No se pudo detener el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}",
|
||||
"service_stopped": "El servicio «{service:s}» se detuvo",
|
||||
"service_unknown": "Servicio desconocido '{service:s}'",
|
||||
"ssowat_conf_generated": "Se ha generado la configuración de SSOwat",
|
||||
"ssowat_conf_updated": "La configuración de SSOwat ha sido actualizada",
|
||||
"system_upgraded": "El sistema ha sido actualizado",
|
||||
"system_username_exists": "El nombre de usuario ya existe en el sistema",
|
||||
"ssowat_conf_generated": "Generada la configuración de SSOwat",
|
||||
"ssowat_conf_updated": "Actualizada la configuración de SSOwat",
|
||||
"system_upgraded": "Sistema actualizado",
|
||||
"system_username_exists": "El nombre de usuario ya existe en la lista de usuarios del sistema",
|
||||
"unbackup_app": "La aplicación '{app:s}' no se guardará",
|
||||
"unexpected_error": "Ha ocurrido un error inesperado",
|
||||
"unexpected_error": "Algo inesperado salió mal: {error}",
|
||||
"unit_unknown": "Unidad desconocida '{unit:s}'",
|
||||
"unlimit": "Sin cuota",
|
||||
"unrestore_app": "La aplicación '{app:s}' no será restaurada",
|
||||
"update_cache_failed": "No se pudo actualizar la caché de APT",
|
||||
"updating_apt_cache": "Actualizando lista de paquetes disponibles...",
|
||||
"updating_apt_cache": "Obteniendo las actualizaciones disponibles para los paquetes del sistema…",
|
||||
"upgrade_complete": "Actualización finalizada",
|
||||
"upgrading_packages": "Actualizando paquetes...",
|
||||
"upgrading_packages": "Actualizando paquetes…",
|
||||
"upnp_dev_not_found": "No se encontró ningún dispositivo UPnP",
|
||||
"upnp_disabled": "UPnP ha sido deshabilitado",
|
||||
"upnp_enabled": "UPnP ha sido habilitado",
|
||||
"upnp_port_open_failed": "No se pudieron abrir puertos por UPnP",
|
||||
"user_created": "El usuario ha sido creado",
|
||||
"user_creation_failed": "No se pudo crear el usuario",
|
||||
"user_deleted": "El usuario ha sido eliminado",
|
||||
"user_deletion_failed": "No se pudo eliminar el usuario",
|
||||
"user_home_creation_failed": "No se puede crear el directorio de usuario 'home'",
|
||||
"user_info_failed": "No se pudo extraer la información del usuario",
|
||||
"upnp_disabled": "UPnP desactivado",
|
||||
"upnp_enabled": "UPnP activado",
|
||||
"upnp_port_open_failed": "No se pudo abrir el puerto vía UPnP",
|
||||
"user_created": "Usuario creado",
|
||||
"user_creation_failed": "No se pudo crear el usuario {user}: {error}",
|
||||
"user_deleted": "Usuario eliminado",
|
||||
"user_deletion_failed": "No se pudo eliminar el usuario {user}: {error}",
|
||||
"user_home_creation_failed": "No se pudo crear la carpeta «home» para el usuario",
|
||||
"user_info_failed": "No se pudo obtener la información del usuario",
|
||||
"user_unknown": "Usuario desconocido: {user:s}",
|
||||
"user_update_failed": "No se pudo actualizar el usuario",
|
||||
"user_updated": "El usuario ha sido actualizado",
|
||||
"user_update_failed": "No se pudo actualizar el usuario {user}: {error}",
|
||||
"user_updated": "Cambiada la información de usuario",
|
||||
"yunohost_already_installed": "YunoHost ya está instalado",
|
||||
"yunohost_ca_creation_failed": "No se pudo crear el certificado de autoridad",
|
||||
"yunohost_configured": "YunoHost ha sido configurado",
|
||||
"yunohost_installing": "Instalando YunoHost...",
|
||||
"yunohost_not_installed": "YunoHost no está instalado o ha habido errores en la instalación. Ejecute 'yunohost tools postinstall'",
|
||||
"ldap_init_failed_to_create_admin": "La inicialización de LDAP falló al crear el usuario administrador",
|
||||
"mailbox_used_space_dovecot_down": "El servicio de e-mail Dovecot debe estar funcionando si desea obtener el espacio utilizado por el buzón de correo",
|
||||
"ssowat_persistent_conf_read_error": "Error al leer la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON",
|
||||
"ssowat_persistent_conf_write_error": "Error al guardar la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON",
|
||||
"yunohost_ca_creation_failed": "No se pudo crear la autoridad de certificación",
|
||||
"yunohost_configured": "YunoHost está ahora configurado",
|
||||
"yunohost_installing": "Instalando YunoHost…",
|
||||
"yunohost_not_installed": "YunoHost no está correctamente instalado. Ejecute «yunohost tools postinstall»",
|
||||
"ldap_init_failed_to_create_admin": "La inicialización de LDAP no pudo crear el usuario «admin»",
|
||||
"mailbox_used_space_dovecot_down": "El servicio de correo Dovecot debe estar funcionando si desea obtener el espacio usado por el buzón de correo",
|
||||
"ssowat_persistent_conf_read_error": "No se pudo leer la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON",
|
||||
"ssowat_persistent_conf_write_error": "No se pudo guardar la configuración persistente de SSOwat: {error:s}. Edite el archivo /etc/ssowat/conf.json.persistent para corregir la sintaxis de JSON",
|
||||
"certmanager_attempt_to_replace_valid_cert": "Está intentando sobrescribir un certificado correcto y válido para el dominio {domain:s}! (Use --force para omitir este mensaje)",
|
||||
"certmanager_domain_unknown": "Dominio desconocido {domain:s}",
|
||||
"certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use --force para omitir este mensaje)",
|
||||
"certmanager_certificate_fetching_or_enabling_failed": "Parece que al habilitar el nuevo certificado para el dominio {domain:s} ha fallado de alguna manera...",
|
||||
"certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio {domain:s} no ha sido emitido por Let's Encrypt. ¡No se puede renovar automáticamente!",
|
||||
"certmanager_attempt_to_renew_valid_cert": "El certificado para el dominio {domain:s} no está a punto de expirar! Utilice --force para omitir este mensaje",
|
||||
"certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} a través de HTTP. Compruebe que la configuración del DNS y de nginx es correcta",
|
||||
"certmanager_error_no_A_record": "No se ha encontrado un registro DNS 'A' para el dominio {domain:s}. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado Let's Encrypt. (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)",
|
||||
"certmanager_domain_dns_ip_differs_from_public_ip": "El registro DNS 'A' para el dominio {domain:s} es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (existen algunos controladores de propagación DNS disponibles en línea). (Si sabe lo que está haciendo, use --no-checks para desactivar esas comprobaciones.)",
|
||||
"certmanager_domain_unknown": "Dominio desconocido «{domain:s}»",
|
||||
"certmanager_domain_cert_not_selfsigned": "El certificado para el dominio {domain:s} no es un certificado autofirmado. ¿Está seguro de que quiere reemplazarlo? (Use «--force» para hacerlo)",
|
||||
"certmanager_certificate_fetching_or_enabling_failed": "El intento de usar el nuevo certificado para {domain:s} no ha funcionado…",
|
||||
"certmanager_attempt_to_renew_nonLE_cert": "El certificado para el dominio «{domain:s}» no ha sido emitido por Let's Encrypt. ¡No se puede renovar automáticamente!",
|
||||
"certmanager_attempt_to_renew_valid_cert": "¡El certificado para el dominio «{domain:s}» no está a punto de expirar! (Puede usar --force si sabe lo que está haciendo)",
|
||||
"certmanager_domain_http_not_working": "Parece que no se puede acceder al dominio {domain:s} a través de HTTP. Compruebe que la configuración del DNS y de NGINX es correcta",
|
||||
"certmanager_error_no_A_record": "No se ha encontrado un registro DNS «A» para el dominio {domain:s}. Debe hacer que su nombre de dominio apunte a su máquina para poder instalar un certificado de Let's Encrypt. (Si sabe lo que está haciendo, use «--no-checks» para desactivar esas comprobaciones.)",
|
||||
"certmanager_domain_dns_ip_differs_from_public_ip": "El registro «A» del DNS para el dominio «{domain:s}» es diferente de la IP de este servidor. Si recientemente modificó su registro A, espere a que se propague (existen algunos verificadores de propagación de DNS disponibles en línea). (Si sabe lo que está haciendo, use «--no-checks» para desactivar esas comprobaciones.)",
|
||||
"certmanager_cannot_read_cert": "Se ha producido un error al intentar abrir el certificado actual para el dominio {domain:s} (archivo: {file:s}), razón: {reason:s}",
|
||||
"certmanager_cert_install_success_selfsigned": "¡Se ha instalado correctamente un certificado autofirmado para el dominio {domain:s}!",
|
||||
"certmanager_cert_install_success": "¡Se ha instalado correctamente un certificado Let's Encrypt para el dominio {domain:s}!",
|
||||
"certmanager_cert_renew_success": "¡Se ha renovado correctamente el certificado Let's Encrypt para el dominio {domain:s}!",
|
||||
"certmanager_cert_install_success_selfsigned": "Instalado correctamente un certificado autofirmado para el dominio «{domain:s}»",
|
||||
"certmanager_cert_install_success": "Instalado correctamente un certificado de Let's Encrypt para el dominio «{domain:s}»",
|
||||
"certmanager_cert_renew_success": "Renovado correctamente el certificado de Let's Encrypt para el dominio «{domain:s}»",
|
||||
"certmanager_old_letsencrypt_app_detected": "\nYunohost ha detectado que la aplicación 'letsencrypt' está instalada, esto produce conflictos con las nuevas funciones de administración de certificados integradas en Yunohost. Si desea utilizar las nuevas funciones integradas, ejecute los siguientes comandos para migrar su instalación:\n\n Yunohost app remove letsencrypt\n Yunohost domain cert-install\n\nP.D.: esto intentará reinstalar los certificados para todos los dominios con un certificado Let's Encrypt o con un certificado autofirmado",
|
||||
"certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para el conjunto de dominios {domain:s}. Por favor, inténtelo de nuevo más tarde. Consulte https://letsencrypt.org/docs/rate-limits/ para obtener más detalles",
|
||||
"certmanager_hit_rate_limit": "Se han emitido demasiados certificados recientemente para este conjunto exacto de dominios {domain:s}. Pruebe de nuevo más tarde. Vea para más detalles https://letsencrypt.org/docs/rate-limits/",
|
||||
"certmanager_cert_signing_failed": "No se pudo firmar el nuevo certificado",
|
||||
"certmanager_no_cert_file": "No se puede leer el certificado para el dominio {domain:s} (archivo: {file:s})",
|
||||
"certmanager_conflicting_nginx_file": "No se puede preparar el dominio para el desafío ACME: el archivo de configuración nginx {filepath:s} está en conflicto y debe ser eliminado primero",
|
||||
"domain_cannot_remove_main": "No se puede eliminar el dominio principal. Primero debe establecer un nuevo dominio principal",
|
||||
"certmanager_self_ca_conf_file_not_found": "No se ha encontrado el archivo de configuración para la autoridad de autofirma (file: {file:s})",
|
||||
"certmanager_unable_to_parse_self_CA_name": "No se puede procesar el nombre de la autoridad de autofirma (file: {file:s} 1)",
|
||||
"certmanager_no_cert_file": "No se pudo leer el certificado para el dominio {domain:s} (archivo: {file:s})",
|
||||
"certmanager_conflicting_nginx_file": "No se pudo preparar el dominio para el desafío ACME: el archivo de configuración de NGINX {filepath:s} está en conflicto y debe ser eliminado primero",
|
||||
"domain_cannot_remove_main": "No se puede eliminar el dominio principal. Configure uno primero",
|
||||
"certmanager_self_ca_conf_file_not_found": "No se pudo encontrar el archivo de configuración para la autoridad de autofirma (archivo: {file:s})",
|
||||
"certmanager_unable_to_parse_self_CA_name": "No se pudo procesar el nombre de la autoridad de autofirma (archivo: {file:s})",
|
||||
"domains_available": "Dominios disponibles:",
|
||||
"backup_archive_broken_link": "Imposible acceder a la copia de seguridad (enlace roto {path:s})",
|
||||
"certmanager_domain_not_resolved_locally": "Su servidor Yunohost no consigue resolver el dominio {domain:s}. Esto puede suceder si ha modificado su registro DNS. Si es el caso, espere unas horas hasta que se propague la modificación. Si el problema persiste, considere añadir {domain:s} a /etc/hosts. (Si sabe lo que está haciendo, use --no-checks para deshabilitar estas verificaciones.)",
|
||||
"certmanager_acme_not_configured_for_domain": "El certificado para el dominio {domain:s} no parece instalado correctamente. Ejecute primero cert-install para este dominio.",
|
||||
"certmanager_http_check_timeout": "Plazo expirado, el servidor no ha podido contactarse a si mismo a través de HTTP usando su dirección IP pública (dominio {domain:s} con ip {ip:s}). Puede ser debido a hairpinning o a una mala configuración del cortafuego/router al que está conectado su servidor.",
|
||||
"certmanager_couldnt_fetch_intermediate_cert": "Plazo expirado, no se ha podido descargar el certificado intermedio de Let's Encrypt. La instalación/renovación del certificado ha sido cancelada - vuelva a intentarlo más tarde.",
|
||||
"appslist_retrieve_bad_format": "El archivo obtenido para la lista de aplicaciones {appslist:s} no es válido",
|
||||
"domain_hostname_failed": "Error al establecer nuevo nombre de host",
|
||||
"yunohost_ca_creation_success": "Se ha creado la autoridad de certificación local.",
|
||||
"app_already_installed_cant_change_url": "Esta aplicación ya está instalada. No se puede cambiar el URL únicamente mediante esta función. Compruebe si está disponible la opción 'app changeurl'.",
|
||||
"backup_archive_broken_link": "No se pudo acceder al archivo de respaldo (enlace roto a {path:s})",
|
||||
"certmanager_domain_not_resolved_locally": "El dominio {domain:s} no puede ser resuelto desde su servidor de YunoHost. Esto puede suceder si ha modificado su registro DNS recientemente. De ser así, espere unas horas para que se propague. Si el problema continúa, considere añadir {domain:s} a /etc/hosts. (Si sabe lo que está haciendo, use «--no-checks» para desactivar esas comprobaciones.)",
|
||||
"certmanager_acme_not_configured_for_domain": "El certificado para el dominio «{domain:s}» no parece que esté instalado correctamente. Ejecute primero «cert-install» para este dominio.",
|
||||
"certmanager_http_check_timeout": "Tiempo de espera agotado cuando el servidor intentaba conectarse consigo mismo a través de HTTP usando una dirección IP pública (dominio «{domain:s}» con IP «{ip:s}»). Puede que esté experimentando un problema de redirección («hairpinning»), o que el cortafuegos o el enrutador de su servidor esté mal configurado.",
|
||||
"certmanager_couldnt_fetch_intermediate_cert": "Tiempo de espera agotado intentando obtener el certificado intermedio de Let's Encrypt. Cancelada la instalación o renovación del certificado. Vuelva a intentarlo más tarde.",
|
||||
"appslist_retrieve_bad_format": "No se pudo leer la lista de aplicaciones obtenida «{appslist:s}»",
|
||||
"domain_hostname_failed": "No se pudo establecer un nuevo nombre de anfitrión («hostname»). Esto podría causar problemas más tarde (no es seguro... podría ir bien).",
|
||||
"yunohost_ca_creation_success": "Creada la autoridad de certificación local.",
|
||||
"app_already_installed_cant_change_url": "Esta aplicación ya está instalada. No se puede cambiar el URL únicamente mediante esta función. Compruebe si está disponible la opción `app changeurl`.",
|
||||
"app_change_no_change_url_script": "La aplicacion {app_name:s} aún no permite cambiar su URL, es posible que deba actualizarla.",
|
||||
"app_change_url_failed_nginx_reload": "No se pudo recargar nginx. Compruebe la salida de 'nginx -t':\n{nginx_errors:s}",
|
||||
"app_change_url_failed_nginx_reload": "No se pudo recargar NGINX. Esta es la salida de «nginx -t»:\n{nginx_errors:s}",
|
||||
"app_change_url_identical_domains": "El antiguo y nuevo dominio/url_path son idénticos ('{domain:s} {path:s}'), no se realizarán cambios.",
|
||||
"app_change_url_no_script": "Esta aplicación '{app_name:s}' aún no permite modificar su URL. Quizás debería actualizar la aplicación.",
|
||||
"app_change_url_success": "El URL de la aplicación {app:s} ha sido cambiado correctamente a {domain:s} {path:s}",
|
||||
"app_location_unavailable": "Este URL no está disponible o está en conflicto con otra aplicación instalada:\n{apps:s}",
|
||||
"app_change_url_no_script": "La aplicación «{app_name:s}» aún no permite la modificación de URLs. Quizás debería actualizarla.",
|
||||
"app_change_url_success": "El URL de la aplicación {app:s} es ahora {domain:s} {path:s}",
|
||||
"app_location_unavailable": "Este URL o no está disponible o está en conflicto con otra(s) aplicación(es) instalada(s):\n{apps:s}",
|
||||
"app_already_up_to_date": "La aplicación {app:s} ya está actualizada",
|
||||
"appslist_name_already_tracked": "Ya existe una lista de aplicaciones registrada con el nombre {name:s}.",
|
||||
"appslist_url_already_tracked": "Ya existe una lista de aplicaciones registrada con el URL {url:s}.",
|
||||
"appslist_migrating": "Migrando la lista de aplicaciones {appslist:s} ...",
|
||||
"appslist_could_not_migrate": "No se pudo migrar la lista de aplicaciones {appslist:s}! No se pudo analizar el URL ... El antiguo cronjob se ha mantenido en {bkp_file:s}.",
|
||||
"appslist_name_already_tracked": "Ya existe una lista de aplicaciones registradas con el nombre {name:s}.",
|
||||
"appslist_url_already_tracked": "Ya existe una lista de aplicaciones registradas con el URL {url:s}.",
|
||||
"appslist_migrating": "Migrando la lista de aplicaciones «{appslist:s}»…",
|
||||
"appslist_could_not_migrate": "¡No se pudo migrar la lista de aplicaciones «{appslist:s}»! No se pudo analizar el URL… El antiguo trabajo de cron se mantuvo en {bkp_file:s}.",
|
||||
"appslist_corrupted_json": "No se pudieron cargar las listas de aplicaciones. Parece que {filename:s} está dañado.",
|
||||
"invalid_url_format": "Formato de URL no válido",
|
||||
"invalid_url_format": "Algo va mal con el URL",
|
||||
"app_upgrade_some_app_failed": "No se pudieron actualizar algunas aplicaciones",
|
||||
"app_make_default_location_already_used": "No puede hacer la aplicación '{app}' por defecto en el dominio {domain} dado que está siendo usado por otra aplicación '{other_app}'",
|
||||
"app_upgrade_app_name": "Actualizando la aplicación {app}...",
|
||||
"app_make_default_location_already_used": "No puede hacer que la aplicación «{app}» sea la predeterminada en el dominio, «{domain}» ya está siendo usado por otra aplicación «{other_app}»",
|
||||
"app_upgrade_app_name": "Actualizando ahora {app}…",
|
||||
"ask_path": "Camino",
|
||||
"backup_abstract_method": "Este método de backup no ha sido implementado aún",
|
||||
"backup_applying_method_borg": "Enviando todos los ficheros al backup en el repositorio borg-backup...",
|
||||
"backup_applying_method_copy": "Copiado todos los ficheros al backup...",
|
||||
"backup_applying_method_custom": "Llamando el método de backup {method:s} ...",
|
||||
"backup_applying_method_tar": "Creando el archivo tar de backup...",
|
||||
"backup_archive_mount_failed": "Fallo en el montado del archivo de backup",
|
||||
"backup_archive_system_part_not_available": "La parte del sistema {part:s} no está disponible en este backup",
|
||||
"backup_archive_writing_error": "No se pueden añadir archivos de backup en el archivo comprimido",
|
||||
"backup_ask_for_copying_if_needed": "Algunos ficheros no pudieron ser preparados para hacer backup usando el método que evita el gasto de espacio temporal en el sistema. Para hacer el backup, {size:s} MB deberían ser usados temporalmente. ¿Está de acuerdo?",
|
||||
"backup_borg_not_implemented": "Método de backup Borg no está implementado aún",
|
||||
"backup_cant_mount_uncompress_archive": "No se puede montar en modo solo lectura el directorio del archivo descomprimido",
|
||||
"backup_abstract_method": "Este método de respaldo aún no se ha implementado",
|
||||
"backup_applying_method_borg": "Enviando todos los archivos para la copia de seguridad al repositorio de borg-backup…",
|
||||
"backup_applying_method_copy": "Copiando todos los archivos a la copia de seguridad…",
|
||||
"backup_applying_method_custom": "Llamando al método de copia de seguridad personalizado «{method:s}»…",
|
||||
"backup_applying_method_tar": "Creando el archivo TAR de respaldo…",
|
||||
"backup_archive_mount_failed": "No se pudo montar el archivo de respaldo",
|
||||
"backup_archive_system_part_not_available": "La parte del sistema «{part:s}» no está disponible en esta copia de seguridad",
|
||||
"backup_archive_writing_error": "No se pudieron añadir los archivos «{source:s}» (llamados en el archivo «{dest:s}») para ser respaldados en el archivo comprimido «{archive:s}»",
|
||||
"backup_ask_for_copying_if_needed": "¿Quiere realizar la copia de seguridad usando {size:s} MB temporalmente? (Se usa este modo ya que algunos archivos no se pudieron preparar usando un método más eficiente.)",
|
||||
"backup_borg_not_implemented": "El método de respaldo de Borg aún no ha sido implementado",
|
||||
"backup_cant_mount_uncompress_archive": "No se pudo montar el archivo descomprimido como protegido contra escritura",
|
||||
"backup_copying_to_organize_the_archive": "Copiando {size:s}MB para organizar el archivo",
|
||||
"backup_couldnt_bind": "No puede enlazar {src:s} con {dest:s}",
|
||||
"backup_csv_addition_failed": "No puede añadir archivos al backup en el archivo CSV",
|
||||
"backup_csv_creation_failed": "No se puede crear el archivo CSV necesario para futuras operaciones de restauración",
|
||||
"backup_custom_mount_error": "Fracaso del método de copia de seguridad personalizada en la etapa \"mount\"",
|
||||
"backup_custom_need_mount_error": "Fracaso del método de copia de seguridad personalizada en la étapa \"need_mount\"",
|
||||
"backup_no_uncompress_archive_dir": "El directorio del archivo descomprimido no existe",
|
||||
"backup_php5_to_php7_migration_may_fail": "No se ha podido convertir su archivo para soportar php7, la restauración de sus aplicaciones php puede fallar (razón : {error:s})",
|
||||
"backup_system_part_failed": "No se puede hacer una copia de seguridad de la parte \"{part:s}\" del sistema",
|
||||
"backup_with_no_backup_script_for_app": "La aplicación {app:s} no tiene script de respaldo. Se ha ignorado.",
|
||||
"backup_with_no_restore_script_for_app": "La aplicación {app:s} no tiene script de restauración, no podrá restaurar automáticamente la copia de seguridad de esta aplicación.",
|
||||
"backup_couldnt_bind": "No se pudo enlazar {src:s} con {dest:s}.",
|
||||
"backup_csv_addition_failed": "No se pudo añadir archivos para respaldar en el archivo CSV",
|
||||
"backup_csv_creation_failed": "No se pudo crear el archivo CSV necesario para la restauración",
|
||||
"backup_custom_mount_error": "El método de respaldo personalizado no pudo superar el paso «mount»",
|
||||
"backup_custom_need_mount_error": "El método de respaldo personalizado no pudo superar el paso «need_mount»",
|
||||
"backup_no_uncompress_archive_dir": "No existe tal directorio de archivos sin comprimir",
|
||||
"backup_php5_to_php7_migration_may_fail": "No se pudo convertir su archivo para que sea compatible con PHP 7, puede que no pueda restaurar sus aplicaciones de PHP (motivo: {error:s})",
|
||||
"backup_system_part_failed": "No se pudo respaldar la parte del sistema «{part:s}»",
|
||||
"backup_with_no_backup_script_for_app": "La aplicación «{app:s}» no tiene un guión de respaldo. Omitiendo.",
|
||||
"backup_with_no_restore_script_for_app": "La aplicación «{app:s}» no tiene un guión de restauración, no podrá restaurar automáticamente la copia de seguridad de esta aplicación.",
|
||||
"dyndns_could_not_check_provide": "No se pudo verificar si {provider:s} puede ofrecer {domain:s}.",
|
||||
"dyndns_domain_not_provided": "El proveedor Dyndns {provider:s} no puede proporcionar el dominio {domain:s}.",
|
||||
"experimental_feature": "Cuidado : esta funcionalidad es experimental y no es considerada estable, no debería usarla excepto si sabe lo que hace.",
|
||||
"good_practices_about_user_password": "Está a punto de establecer una nueva contraseña de usuario. La contraseña debería de ser de al menos 8 caracteres, aunque es una buena práctica usar una contraseña más larga (es decir, una frase de paso) y/o usar varias clases de caracteres (mayúsculas, minúsculas, dígitos y caracteres especiales).",
|
||||
"password_listed": "Esta contraseña es una de las más usadas en el mundo. Elija algo un poco más único.",
|
||||
"dyndns_domain_not_provided": "El proveedor de DynDNS {provider:s} no puede proporcionar el dominio {domain:s}.",
|
||||
"experimental_feature": "Aviso : esta funcionalidad es experimental y no se considera estable, no debería usarla a menos que sepa lo que está haciendo.",
|
||||
"good_practices_about_user_password": "Está a punto de establecer una nueva contraseña de usuario. La contraseña debería de ser de al menos 8 caracteres, aunque es una buena práctica usar una contraseña más extensa (básicamente una frase) y/o usar caracteres de varias clases (mayúsculas, minúsculas, números y caracteres especiales).",
|
||||
"password_listed": "Esta contraseña es una de las más usadas en el mundo. Elija algo más único.",
|
||||
"password_too_simple_1": "La contraseña debe tener al menos 8 caracteres de longitud",
|
||||
"password_too_simple_2": "La contraseña debe tener al menos 8 caracteres de longitud y contiene dígitos, mayúsculas y minúsculas",
|
||||
"password_too_simple_3": "La contraseña debe tener al menos 8 caracteres de longitud y contiene dígitos, mayúsculas, minúsculas y caracteres especiales",
|
||||
"password_too_simple_4": "La contraseña debe tener al menos 12 caracteres de longitud y contiene dígitos, mayúsculas, minúsculas y caracteres especiales"
|
||||
"password_too_simple_2": "La contraseña tiene que ser de al menos 8 caracteres de longitud e incluir un número y caracteres en mayúsculas y minúsculas",
|
||||
"password_too_simple_3": "La contraseña tiene que ser de al menos 8 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales",
|
||||
"password_too_simple_4": "La contraseña tiene que ser de al menos 12 caracteres de longitud e incluir un número, mayúsculas, minúsculas y caracteres especiales",
|
||||
"users_available": "Usuarios disponibles:",
|
||||
"user_not_in_group": "El usuario «{user:s}» no está en el grupo «{group:s}»",
|
||||
"user_already_in_group": "El usuario «{user:}» ya está en el grupo «{group:s}»",
|
||||
"updating_app_lists": "Obteniendo actualizaciones disponibles para las aplicaciones…",
|
||||
"update_apt_cache_warning": "Algo fue mal durante la actualización de la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}",
|
||||
"update_apt_cache_failed": "No se pudo actualizar la caché de APT (gestor de paquetes de Debian). Aquí tiene un volcado de las líneas de sources.list que podría ayudarle a identificar las líneas problemáticas:\n{sourceslist}",
|
||||
"tools_upgrade_special_packages_completed": "Actualización de paquetes de YunoHost completada.\nPulse [Intro] para regresar a la línea de órdenes",
|
||||
"tools_upgrade_special_packages_explanation": "Esta acción terminará pero la actualización especial real continuará en segundo plano. No inicie ninguna otra acción en su servidor en aproximadamente 10 minutos (dependiendo de la velocidad de su hardware). Una vez hecho, podría tener que volver a iniciar sesión en la administración web. El registro de actualización estará disponible en Herramientas → Registro (en la página de administración web) o mediante «yunohost log list» (desde la línea de órdenes).",
|
||||
"tools_upgrade_special_packages": "Actualizando ahora paquetes «especiales» (relacionados con YunoHost)…",
|
||||
"tools_upgrade_regular_packages_failed": "No se pudieron actualizar los paquetes: {packages_list}",
|
||||
"tools_upgrade_regular_packages": "Actualizando ahora paquetes «normales» (no relacionados con YunoHost)…",
|
||||
"tools_upgrade_cant_unhold_critical_packages": "No se pudo liberar los paquetes críticos…",
|
||||
"tools_upgrade_cant_hold_critical_packages": "No se pudieron retener los paquetes críticos…",
|
||||
"tools_upgrade_cant_both": "No se puede actualizar el sistema y las aplicaciones al mismo tiempo",
|
||||
"tools_upgrade_at_least_one": "Especifique «--apps», o «--system»",
|
||||
"tools_update_failed_to_app_fetchlist": "No se pudo actualizar la lista de aplicaciones de YunoHost porque: {error}",
|
||||
"this_action_broke_dpkg": "Esta acción rompió dpkg/APT(los gestores de paquetes del sistema)… Puede tratar de solucionar este problema conectando mediante SSH y ejecutando `sudo dpkg --configure -a`.",
|
||||
"system_groupname_exists": "El nombre de grupo ya existe en el grupo del sistema",
|
||||
"service_reloaded_or_restarted": "El servicio «{service:s}» ha sido recargado o reiniciado",
|
||||
"service_reload_or_restart_failed": "No se pudo recargar o reiniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}",
|
||||
"service_restarted": "Reiniciado el servicio «{service:s}»",
|
||||
"service_restart_failed": "No se pudo reiniciar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}",
|
||||
"service_reloaded": "El servicio «{service:s}» ha sido recargado",
|
||||
"service_reload_failed": "No se pudo recargar el servicio «{service:s}»\n\nRegistro de servicios recientes:{logs:s}",
|
||||
"service_regen_conf_is_deprecated": "¡«yunohost service regen-conf» está obsoleto! Use «yunohost tools regen-conf» en su lugar.",
|
||||
"service_description_yunohost-firewall": "Gestiona los puertos de conexiones abiertos y cerrados a los servicios",
|
||||
"service_description_yunohost-api": "Gestiona las interacciones entre la interfaz web de YunoHost y el sistema",
|
||||
"service_description_ssh": "Permite conectar a su servidor remotamente mediante un terminal (protocolo SSH)",
|
||||
"service_description_slapd": "Almacena usuarios, dominios e información relacionada",
|
||||
"service_description_rspamd": "Filtra correo no deseado y otras características relacionadas con el correo",
|
||||
"service_description_rmilter": "Comprueba varios parámetros en el correo",
|
||||
"service_description_redis-server": "Una base de datos especializada usada para el acceso rápido de datos, cola de tareas y comunicación entre programas",
|
||||
"service_description_postfix": "Usado para enviar y recibir correos",
|
||||
"service_description_php7.0-fpm": "Ejecuta aplicaciones escritas en PHP con NGINX",
|
||||
"service_description_nslcd": "Maneja la conexión del intérprete de órdenes («shell») de usuario de YunoHost",
|
||||
"service_description_nginx": "Sirve o proporciona acceso a todos los sitios web alojados en su servidor",
|
||||
"service_description_mysql": "Almacena los datos de la aplicación (base de datos SQL)",
|
||||
"service_description_metronome": "Gestionar las cuentas XMPP de mensajería instantánea",
|
||||
"service_description_glances": "Supervisa la información del sistema en su servidor",
|
||||
"service_description_fail2ban": "Protege contra ataques de fuerza bruta y otras clases de ataques desde Internet",
|
||||
"service_description_dovecot": "Permite a los clientes de correo acceder/obtener correo (vía IMAP y POP3)",
|
||||
"service_description_dnsmasq": "Maneja la resolución de nombres de dominio (DNS)",
|
||||
"service_description_avahi-daemon": "Permite acceder a su servidor usando «yunohost.local» en su red local",
|
||||
"server_reboot_confirm": "El servidor se reiniciará inmediatamente ¿está seguro? [{answers:s}]",
|
||||
"server_reboot": "El servidor se reiniciará",
|
||||
"server_shutdown_confirm": "El servidor se apagará inmediatamente ¿está seguro? [{answers:s}]",
|
||||
"server_shutdown": "El servidor se apagará",
|
||||
"root_password_replaced_by_admin_password": "Su contraseña de root ha sido sustituida por su contraseña de administración.",
|
||||
"root_password_desynchronized": "La contraseña de administración ha sido cambiada pero ¡YunoHost no pudo propagar esto a la contraseña de root!",
|
||||
"restore_system_part_failed": "No se pudo restaurar la parte del sistema «{part:s}»",
|
||||
"restore_removing_tmp_dir_failed": "No se pudo eliminar un directorio temporal antiguo",
|
||||
"restore_not_enough_disk_space": "Espacio insuficiente (espacio: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)",
|
||||
"restore_mounting_archive": "Montando archivo en «{path:s}»",
|
||||
"restore_may_be_not_enough_disk_space": "Parece que su sistema no tiene suficiente espacio (libre: {free_space:d} B, espacio necesario: {needed_space:d} B, margen de seguridad: {margin:d} B)",
|
||||
"restore_extracting": "Extrayendo los archivos necesarios para el archivo…",
|
||||
"regenconf_pending_applying": "Aplicando la configuración pendiente para la categoría «{category}»…",
|
||||
"regenconf_failed": "No se pudo regenerar la configuración para la(s) categoría(s): {categories}",
|
||||
"regenconf_dry_pending_applying": "Comprobando la configuración pendiente que habría sido aplicada para la categoría «{category}»…",
|
||||
"regenconf_would_be_updated": "La configuración habría sido actualizada para la categoría «{category}»",
|
||||
"regenconf_updated": "Actualizada la configuración para la categoría «{category}»",
|
||||
"regenconf_up_to_date": "Ya está actualizada la configuración para la categoría «{category}»",
|
||||
"regenconf_now_managed_by_yunohost": "El archivo de configuración «{conf}» está gestionado ahora por YunoHost (categoría {category}).",
|
||||
"regenconf_file_updated": "Actualizado el archivo de configuración «{conf}»",
|
||||
"regenconf_file_removed": "Eliminado el archivo de configuración «{conf}»",
|
||||
"regenconf_file_remove_failed": "No se pudo eliminar el archivo de configuración «{conf}»",
|
||||
"regenconf_file_manually_removed": "El archivo de configuración «{conf}» ha sido eliminado manualmente y no se creará",
|
||||
"regenconf_file_manually_modified": "El archivo de configuración «{conf}» ha sido modificado manualmente y no será actualizado",
|
||||
"regenconf_file_kept_back": "Se espera que el archivo de configuración «{conf}» sea eliminado por regen-conf (categoría {category}) pero ha sido retenido.",
|
||||
"regenconf_file_copy_failed": "No se pudo copiar el nuevo archivo de configuración «{new}» a «{conf}»",
|
||||
"regenconf_file_backed_up": "Archivo de configuración «{conf}» respaldado en «{backup}»",
|
||||
"remove_user_of_group_not_allowed": "No tiene permiso para eliminar al usuario «{user:s}» en el grupo «{group:s}»",
|
||||
"remove_main_permission_not_allowed": "No se permite eliminar el permiso principal",
|
||||
"recommend_to_add_first_user": "La posinstalación ha terminado pero YunoHost necesita al menos un usuario para funcionar correctamente, debe añadir uno ejecutando «yunohost user create <nombredeusuario>» o usando la interfaz de administración.",
|
||||
"permission_update_nothing_to_do": "No hay permisos para actualizar",
|
||||
"permission_updated": "Actualizado el permiso «{permission:s}»",
|
||||
"permission_generated": "Actualizada la base de datos de permisos",
|
||||
"permission_update_failed": "No se pudo actualizar el permiso «{permission}» : {error}",
|
||||
"permission_name_not_valid": "Elija un nombre de permiso permitido para «{permission:s}",
|
||||
"permission_not_found": "No se encontró el permiso «{permission:s}»",
|
||||
"permission_deletion_failed": "No se pudo eliminar el permiso «{permission}»: {error}",
|
||||
"permission_deleted": "Eliminado el permiso «{permission:s}»",
|
||||
"permission_creation_failed": "No se pudo crear el permiso «{permission}»: {error}",
|
||||
"permission_created": "Creado el permiso «{permission:s}»",
|
||||
"permission_already_exist": "El permiso «{permission}» ya existe",
|
||||
"permission_already_clear": "El permiso «{permission:s}» ya está definido para la aplicación {app:s}",
|
||||
"pattern_password_app": "Las contraseñas no pueden incluir los siguientes caracteres: {forbidden_chars}",
|
||||
"need_define_permission_before": "Redefina los permisos ejecutando «yunohost user permission add -u USUARIO» antes de eliminar un grupo permitido",
|
||||
"migrations_to_be_ran_manually": "La migración {id} hay que ejecutarla manualmente. Vaya a Herramientas → Migraciones en la página web de administración o ejecute `yunohost tools migrations migrate`.",
|
||||
"migrations_success_forward": "Migración {id} completada",
|
||||
"migrations_skip_migration": "Omitiendo migración {id}…",
|
||||
"migrations_running_forward": "Ejecutando migración {id}…",
|
||||
"migrations_pending_cant_rerun": "Esas migraciones están aún pendientes, así que no se pueden volver a ejecutar: {ids}",
|
||||
"migrations_not_pending_cant_skip": "Esas migraciones no están pendientes, así que no pueden ser omitidas: {ids}",
|
||||
"migrations_no_such_migration": "No hay ninguna migración llamada «{id}»",
|
||||
"migrations_no_migrations_to_run": "No hay migraciones que ejecutar",
|
||||
"migrations_need_to_accept_disclaimer": "Para ejecutar la migración {id} debe aceptar el siguiente descargo de responsabilidad:\n---\n{disclaimer}\n---\nSi acepta ejecutar la migración, vuelva a ejecutar la orden con la opción «--accept-disclaimer».",
|
||||
"migrations_must_provide_explicit_targets": "Necesita proporcionar objetivos explícitos al usar «--skip» or «--force-rerun»",
|
||||
"migrations_migration_has_failed": "La migración {id} no se ha completado, cancelando. Error: {exception}",
|
||||
"migrations_loading_migration": "Cargando migración {id}…",
|
||||
"migrations_list_conflict_pending_done": "No puede usar «--previous» y «--done» al mismo tiempo.",
|
||||
"migrations_exclusive_options": "«--auto», «--skip», and «--force-rerun» son opciones mutuamente excluyentes.",
|
||||
"migrations_failed_to_load_migration": "No se pudo cargar la migración {id}: {error}",
|
||||
"migrations_dependencies_not_satisfied": "Ejecutar estas migraciones: «{dependencies_id}» antes de migrar {id}.",
|
||||
"migrations_cant_reach_migration_file": "No se pudo acceder a los archivos de migración en la ruta «%s»",
|
||||
"migrations_already_ran": "Esas migraciones ya se han realizado: {ids}",
|
||||
"migration_0011_update_LDAP_schema": "Actualizando el esquema de LDAP…",
|
||||
"migration_0011_update_LDAP_database": "Actualizando la base de datos de LDAP…",
|
||||
"migration_0011_rollback_success": "Sistema revertido.",
|
||||
"migration_0011_migration_failed_trying_to_rollback": "No se pudo migrar… intentando revertir el sistema.",
|
||||
"migration_0011_migrate_permission": "Migrando permisos desde la configuración de las aplicaciones a LDAP…",
|
||||
"migration_0011_LDAP_update_failed": "No se pudo actualizar LDAP. Error: {error:s}",
|
||||
"migration_0011_LDAP_config_dirty": "Parece que ha personalizado la configuración de LDAP. Para esta migración se necesita actualizar la configuración de LDAP.\nNecesita guardar su configuración actual, reiniciar la configuración original ejecutando «yunohost tools regen-conf -f» y reintentar la migración",
|
||||
"migration_0011_done": "Migración finalizada. Ahora puede gestionar los grupos de usuarios.",
|
||||
"migration_0011_create_group": "Creando un grupo para cada usuario…",
|
||||
"migration_0011_can_not_backup_before_migration": "El respaldo del sistema no se pudo completar antes de que la migración fallase. Error: {error:s}",
|
||||
"migration_0011_backup_before_migration": "Creando un respaldo de la base de datos de LDAP y de la configuración de las aplicaciones antes de la migración real.",
|
||||
"migration_0009_not_needed": "La migración ya ocurrió de algún modo… (?) Omitiendo.",
|
||||
"migration_0008_no_warning": "Ignorar su configuración SSH debería ser seguro ¡aunque esto no se puede prometer! Ejecute la migración para ignorarla. Por otra parte puede omitir la migración, aunque no se recomienda.",
|
||||
"migration_0008_warning": "Si entiende esos avisos y quiere que YunoHost ignore su configuración actual, ejecute la migración. Por otra parte puede omitir la migración, aunque no se recomienda.",
|
||||
"migration_0008_dsa": "• Se desactivará la clave DSA. Así que podría tener que anular un aviso espeluznante de su cliente SSH y volver a comprobar la huella de su servidor;",
|
||||
"migration_0008_root": "• No podrá conectarse como «root» a través de SSH. En su lugar debe usar el usuario «admin»;",
|
||||
"migration_0008_port": "• Tendrá que conectarse usando el puerto 22 en vez de su actual puerto SSH personalizado. No dude en reconfigurarlo;",
|
||||
"migration_0008_general_disclaimer": "Para mejorar la seguridad de su servidor, es recomendable permitir a YunoHost gestionar la configuración de SSH. Su actual configuración de SSH difiere de la recomendación. Si permite a YunoHost reconfigurarla, la manera en la que conecta con su servidor a través de SSH cambiará así:",
|
||||
"migration_0007_cannot_restart": "No se puede reiniciar SSH después de intentar cancelar la migración número 6.",
|
||||
"migration_0007_cancelled": "No se pudo mejorar el modo en el que se gestiona su configuración de SSH.",
|
||||
"migration_0006_disclaimer": "YunoHost espera ahora que las contraseñas de «admin» y «root» estén sincronizadas. Esta migración reemplaza su contraseña de «root» por la contraseña de «admin».",
|
||||
"migration_0005_not_enough_space": "Tenga suficiente espacio libre disponible en {path} para ejecutar la migración.",
|
||||
"migration_0005_postgresql_96_not_installed": "⸘PostgreSQL 9.4 está instalado pero no PostgreSQL 9.6‽ Algo raro podría haber ocurrido en su sistema:(…",
|
||||
"migration_0005_postgresql_94_not_installed": "PostgreSQL no estaba instalado en su sistema. Nada que hacer.",
|
||||
"migration_0003_modified_files": "Tenga en cuenta que se encontró que los siguientes archivos fueron modificados manualmente y podrían ser sobrescritos después de la actualización: {manually_modified_files}",
|
||||
"migration_0003_problematic_apps_warning": "Tenga en cuenta que se detectaron las siguientes aplicaciones instaladas posiblemente problemáticas. Parece que no fueron instaladas desde una lista de aplicaciones o no estaban etiquetadas como «funciona». Así que no hay garantía de que aún funcionen después de la actualización: {problematic_apps}",
|
||||
"migration_0003_general_warning": "Tenga en cuenta que esta migración es una operación delicada. El equipo de YunoHost ha hecho todo lo posible para revisarla y probarla, pero la migración aún podría romper parte del sistema o de sus aplicaciones.\n\nPor lo tanto, se recomienda que:\n - Realice una copia de seguridad de cualquier dato crítico o aplicación. Más información en https://yunohost.org/backup;\n - Tenga paciencia tras iniciar la migración: dependiendo de su conexión a Internet y de su hardware, podría tardar unas cuantas horas hasta que todo se actualice.\n\nAdemás, el puerto para SMTP usado por los clientes de correo externos (como Thunderbird o K9-Mail) cambió de 465 (SSL/TLS) a 587 (STARTTLS). El antiguo puerto (465) se cerrará automáticamente y el nuevo puerto (587) se abrirá en el cortafuegos. Todos los usuarios *tendrán* que adaptar la configuración de sus clientes de correo por lo tanto.",
|
||||
"migration_0003_still_on_jessie_after_main_upgrade": "Algo fue mal durante la actualización principal: ⸘el sistema está aún en Jessie‽ Para investigar el problema, vea {log}:s…",
|
||||
"migration_0003_system_not_fully_up_to_date": "Su sistema no está totalmente actualizado. Realice una actualización normal antes de ejecutar la migración a Stretch.",
|
||||
"migration_0003_not_jessie": "¡La distribución de Debian actual no es Jessie!",
|
||||
"migration_0003_yunohost_upgrade": "Iniciando la actualización del paquete YunoHost… La migración finalizará pero la actualización real ocurrirá inmediatamente después. Después de que la operación esté completada, podría tener que iniciar sesión en la página de administración de nuevo.",
|
||||
"migration_0003_restoring_origin_nginx_conf": "Su archivo /etc/nginx/nginx.conf ha sido editado de algún modo. La migración lo devolverá a su estado original primero… El archivo anterior estará disponible como {backup_dest}.",
|
||||
"migration_0003_fail2ban_upgrade": "Iniciando la actualización de Fail2Ban…",
|
||||
"migration_0003_main_upgrade": "Iniciando la actualización principal…",
|
||||
"migration_0003_patching_sources_list": "Corrigiendo «sources.lists»…",
|
||||
"migration_0003_start": "Iniciando migración a Stretch. El registro estará disponible en {logfile}.",
|
||||
"migration_description_0012_postgresql_password_to_md5_authentication": "Forzar a la autentificación de PostgreSQL a usar MD5 para las conexiones locales",
|
||||
"migration_description_0011_setup_group_permission": "Configurar grupo de usuario y configurar permisos para aplicaciones y servicios",
|
||||
"migration_description_0010_migrate_to_apps_json": "Eliminar las listas de aplicaciones («appslists») obsoletas y usar en su lugar la nueva lista unificada «apps.json»",
|
||||
"migration_description_0009_decouple_regenconf_from_services": "Separar el mecanismo «regen-conf» de los servicios",
|
||||
"migration_description_0008_ssh_conf_managed_by_yunohost_step2": "Permitir que la configuración de SSH la gestione YunoHost (paso 2, manual)",
|
||||
"migration_description_0007_ssh_conf_managed_by_yunohost_step1": "Permitir que la configuración de SSH la gestione YunoHost (paso 1, automático)",
|
||||
"migration_description_0006_sync_admin_and_root_passwords": "Sincronizar las contraseñas de «admin» y «root»",
|
||||
"migration_description_0005_postgresql_9p4_to_9p6": "Migrar las bases de datos de PostgreSQL 9.4 a 9.6",
|
||||
"migration_description_0004_php5_to_php7_pools": "Reconfigurar los «pools» de PHP para usar PHP 7 en vez de 5",
|
||||
"migration_description_0003_migrate_to_stretch": "Actualizar el sistema a Debian Stretch y YunoHost 3.0",
|
||||
"migration_description_0002_migrate_to_tsig_sha256": "Mejore la seguridad de las actualizaciones de TSIG de DynDNS usando SHA-512 en vez de MD5",
|
||||
"migration_description_0001_change_cert_group_to_sslcert": "Cambiar los permisos de grupo de certificados de «metronome» a «ssl-cert»",
|
||||
"migrate_tsig_not_needed": "Parece que no usa un dominio de DynDNS, así que no es necesario migrar.",
|
||||
"migrate_tsig_wait_4": "30 segundos…",
|
||||
"migrate_tsig_wait_3": "1 min. …",
|
||||
"migrate_tsig_wait_2": "2 min. …",
|
||||
"migrate_tsig_wait": "Esperando tres minutos para que el servidor de DynDNS tenga en cuenta la nueva clave…",
|
||||
"migrate_tsig_start": "Detectado algoritmo de clave insuficientemente seguro para la firma TSIG del dominio «{domain}», iniciando migración al más seguro HMAC-SHA-512",
|
||||
"migrate_tsig_failed": "No se pudo migrar el dominio de DynDNS «{domain}» a HMAC-SHA-512, revertiendo. Error: {error_code}, {error}",
|
||||
"migrate_tsig_end": "Terminada la migración a HMAC-SHA-512",
|
||||
"mail_unavailable": "Esta dirección de correo está reservada y será asignada automáticamente al primer usuario",
|
||||
"mailbox_disabled": "Correo desactivado para usuario {user:s}",
|
||||
"log_tools_reboot": "Reiniciar el servidor",
|
||||
"log_tools_shutdown": "Apagar el servidor",
|
||||
"log_tools_upgrade": "Actualizar paquetes del sistema",
|
||||
"log_tools_postinstall": "Posinstalación del servidor YunoHost",
|
||||
"log_tools_migrations_migrate_forward": "Migrar hacia adelante",
|
||||
"log_tools_maindomain": "Convertir «{}» en el dominio principal",
|
||||
"log_user_permission_remove": "Actualizar permiso «{}»",
|
||||
"log_user_permission_add": "Actualizar permiso «{}»",
|
||||
"log_user_update": "Actualizar la información de usuario de «{}»",
|
||||
"log_user_group_update": "Actualizar grupo «{}»",
|
||||
"log_user_group_delete": "Eliminar grupo «{}»",
|
||||
"log_user_group_add": "Añadir grupo «{}»",
|
||||
"log_user_delete": "Eliminar usuario «{}»",
|
||||
"log_user_create": "Añadir usuario «{}»",
|
||||
"log_regen_conf": "Regenerar la configuración del sistema «{}»",
|
||||
"log_letsencrypt_cert_renew": "Renovar el certificado «{}» de Let's Encrypt",
|
||||
"log_selfsigned_cert_install": "Instalar certificado autofirmado en el dominio «{}»",
|
||||
"log_permission_update": "Actualizar permiso «{}» para la aplicación «{}»",
|
||||
"log_permission_remove": "Eliminar permiso «{}»",
|
||||
"log_permission_add": "Añadir el permiso «{}» para la aplicación «{}»",
|
||||
"log_letsencrypt_cert_install": "Instalar un certificado de Let's Encrypt en el dominio «{}»",
|
||||
"log_dyndns_update": "Actualizar la IP asociada con su subdominio de YunoHost «{}»",
|
||||
"log_dyndns_subscribe": "Subscribirse a un subdomino de YunoHost «{}»",
|
||||
"log_domain_remove": "Eliminar el dominio «{}» de la configuración del sistema",
|
||||
"log_domain_add": "Añadir el dominio «{}» a la configuración del sistema",
|
||||
"log_remove_on_failed_install": "Eliminar «{}» después de una instalación fallida",
|
||||
"log_remove_on_failed_restore": "Eliminar «{}» después de una restauración fallida desde un archivo de respaldo",
|
||||
"log_backup_restore_app": "Restaurar «{}» desde un archivo de respaldo",
|
||||
"log_backup_restore_system": "Restaurar sistema desde un archivo de respaldo",
|
||||
"log_available_on_yunopaste": "Este registro está ahora disponible vía {url}",
|
||||
"log_app_makedefault": "Convertir «{}» en aplicación predeterminada",
|
||||
"log_app_upgrade": "Actualizar la aplicación «{}»",
|
||||
"log_app_remove": "Eliminar la aplicación «{}»",
|
||||
"log_app_install": "Instalar la aplicación «{}»",
|
||||
"log_app_change_url": "Cambiar el URL de la aplicación «{}»",
|
||||
"log_app_removelist": "Eliminar una lista de aplicaciones",
|
||||
"log_app_fetchlist": "Añadir una lista de aplicaciones",
|
||||
"log_app_clearaccess": "Eliminar todos los accesos a «{}»",
|
||||
"log_app_removeaccess": "Eliminar acceso a «{}»",
|
||||
"log_app_addaccess": "Añadir acceso a «{}»",
|
||||
"log_operation_unit_unclosed_properly": "La unidad de operación no se ha cerrado correctamente",
|
||||
"log_does_exists": "No existe ningún registro de actividades con el nombre «{log}», ejecute «yunohost log list» para ver todos los registros de actividades disponibles",
|
||||
"log_help_to_get_failed_log": "No se pudo completar la operación «{desc}». Para obtener ayuda, comparta el registro completo de esta operación ejecutando la orden «yunohost log display {name} --share»",
|
||||
"log_link_to_failed_log": "No se pudo completar la operación «{desc}». Para obtener ayuda, proporcione el registro completo de esta operación <a href=\"#/tools/logs/{name}\">pulsando aquí</a>",
|
||||
"log_help_to_get_log": "Para ver el registro de la operación «{desc}», ejecute la orden «yunohost log display {name}»",
|
||||
"log_link_to_log": "Registro completo de esta operación: «<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>»",
|
||||
"log_category_404": "La categoría de registro «{category}» no existe",
|
||||
"log_corrupted_md_file": "El archivo de metadatos YAML asociado con el registro está dañado: «{md_file}\nError: {error}»",
|
||||
"hook_json_return_error": "No se pudo leer la respuesta del gancho {path:s}. Error: {msg:s}. Contenido sin procesar: {raw_content}",
|
||||
"group_update_failed": "No se pudo actualizar el grupo «{group}»: {error}",
|
||||
"group_updated": "Grupo «{group}» actualizado",
|
||||
"group_unknown": "El grupo «{group:s}» es desconocido",
|
||||
"group_info_failed": "No se pudo mostrar la información del grupo",
|
||||
"group_deletion_not_allowed": "No se puede eliminar el grupo {group:s} manualmente.",
|
||||
"group_deletion_failed": "No se pudo eliminar el grupo «{group}»: {error}",
|
||||
"group_deleted": "Eliminado el grupo «{group}»",
|
||||
"group_creation_failed": "No se pudo crear el grupo «{group}»: {error}",
|
||||
"group_created": "Creado el grupo «{group}»",
|
||||
"group_name_already_exist": "El grupo {name:s} ya existe",
|
||||
"group_already_disallowed": "El grupo «{group:s}» ya tiene desactivado el permiso «{permission:s}» para la aplicación «{app:s}»",
|
||||
"group_already_allowed": "El grupo «{group:s}» ya tiene activado el permiso «{permission:s}» para la aplicación «{app:s}»",
|
||||
"good_practices_about_admin_password": "Va a establecer una nueva contraseña de administración. La contraseña debería tener al menos 8 caracteres, aunque es una buena práctica usar una contraseña más extensa (básicamente una frase) y/o usar caracteres de varias clases (mayúsculas, minúsculas, números y caracteres especiales).",
|
||||
"global_settings_unknown_type": "Situación imprevista, la configuración {setting:s} parece tener el tipo {unknown_type:s} 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:s}», 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_setting_example_string": "Ejemplo de opción de cadena",
|
||||
"global_settings_setting_example_int": "Ejemplo de opción «int»",
|
||||
"global_settings_setting_example_enum": "Ejemplo de opción «enum»",
|
||||
"global_settings_setting_example_bool": "Ejemplo de opción booleana",
|
||||
"global_settings_reset_success": "Respaldada la configuración previa en {path:s}",
|
||||
"global_settings_key_doesnt_exists": "La clave «{settings_key:s}» 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:s}",
|
||||
"global_settings_cant_serialize_settings": "No se pudo seriar los datos de configuración, motivo: {reason:s}",
|
||||
"global_settings_cant_open_settings": "No se pudo abrir el archivo de configuración, motivo: {reason:s}",
|
||||
"global_settings_bad_type_for_setting": "Tipo erróneo para la configuración {setting:s}, obtuvo {received_type:s}, esperado {expected_type:s}",
|
||||
"global_settings_bad_choice_for_enum": "Opción errónea para la configuración {setting:s}, obtuvo «{choice:s}» pero las opciones disponibles son: {available_choices:s}",
|
||||
"file_does_not_exist": "El archivo {path:s} no existe.",
|
||||
"error_when_removing_sftpuser_group": "No se pudo eliminar el grupo sftpusers",
|
||||
"edit_permission_with_group_all_users_not_allowed": "No puede editar el permiso para el grupo «all_users», utilice «yunohost user permission clear APLICACIÓN» o «yunohost user permission add APLICACIÓN -u USUARIO».",
|
||||
"edit_group_not_allowed": "No tiene permiso para editar el grupo {group:s}",
|
||||
"dyndns_could_not_check_available": "No se pudo comprobar si {domain:s} está disponible en {provider:s}.",
|
||||
"domain_dyndns_dynette_is_unreachable": "No se pudo conectar a dynette de YunoHost. O su YunoHost no está correctamente conectado a Internet o el servidor dynette está caído. Error: {error}",
|
||||
"domain_dns_conf_is_just_a_recommendation": "Esta orden muestra la configuración *recomendada*. No configura el DNS en realidad. Es su responsabilidad configurar la zona de DNS en su registrador según esta recomendación.",
|
||||
"dpkg_lock_not_available": "Esta orden no se puede ejecutar en este momento porque otro programa parece que está usando el bloqueo de dpkg (el gestor de paquetes del sistema)",
|
||||
"dpkg_is_broken": "No puede hacer esto en este momento porque dpkg/apt (los gestores de paquetes del sistema) parecen estar en un estado roto... Puede tratar de solucionar este problema conectando a través de SSH y ejecutando `sudo dpkg --configure -a`.",
|
||||
"confirm_app_install_thirdparty": "¡PELIGRO! Esta aplicación no forma parte del catálogo de aplicaciones de YunoHost. Instalar aplicaciones de terceros podría comprometer la integridad y seguridad de su sistema. Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema… Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»",
|
||||
"confirm_app_install_danger": "¡PELIGRO! ¡Esta aplicación es conocida por ser aún experimental (o no funciona explícitamente)! Probablemente NO debería instalarla salvo que sepa lo que está haciendo. No tendrá NINGUNA AYUDA si esta aplicación no funciona o rompe su sistema… Si está dispuesto a aceptar ese riesgo de todas formas, escriba «{answers:s}»",
|
||||
"confirm_app_install_warning": "Aviso: esta aplicación puede funcionar pero no está bien integrada en YunoHost. Algunas herramientas como la autentificación única y respaldo/restauración podrían no estar disponibles. ¿Instalar de todos modos? [{answers:s}] ",
|
||||
"backup_unable_to_organize_files": "No se pudo usar el método rápido de organización de los archivos en el archivo",
|
||||
"backup_permission": "Permiso de respaldo para la aplicación {app:s}",
|
||||
"backup_output_symlink_dir_broken": "El directorio de su archivo «{path:s}» es un enlace simbólico roto. Tal vez olvidó (re)montarlo o conectarlo al medio de almacenamiento al que apunta.",
|
||||
"backup_mount_archive_for_restore": "Preparando el archivo para la restauración…",
|
||||
"backup_method_tar_finished": "Creado el archivo TAR de respaldo",
|
||||
"backup_method_custom_finished": "Terminado el método «{method:s}» de respaldo personalizado",
|
||||
"backup_method_copy_finished": "Terminada la copia de seguridad",
|
||||
"backup_method_borg_finished": "Terminado el respaldo en Borg",
|
||||
"backup_custom_backup_error": "El método de respaldo personalizado no pudo superar el paso de «copia de seguridad»",
|
||||
"backup_actually_backuping": "Creando un archivo de respaldo de los archivos obtenidos…",
|
||||
"ask_new_path": "Nueva ruta",
|
||||
"ask_new_domain": "Nuevo dominio",
|
||||
"apps_permission_restoration_failed": "Otorgar el permiso «{permission:s}» para restaurar {app:s}",
|
||||
"apps_permission_not_found": "No se han encontrado permisos para las aplicaciones instaladas",
|
||||
"app_upgrade_several_apps": "Las siguientes aplicaciones se actualizarán: {apps}",
|
||||
"app_start_restore": "Restaurando aplicación «{app}»…",
|
||||
"app_start_backup": "Obteniendo archivos para el respaldo de «{app}»…",
|
||||
"app_start_remove": "Eliminando aplicación «{app}»…",
|
||||
"app_start_install": "Instalando aplicación «{app}»…",
|
||||
"app_not_upgraded": "Error al actualizar la aplicación «{failed_app}» y como consecuencia se han cancelado las actualizaciones de las siguientes aplicaciones: {apps}",
|
||||
"app_action_cannot_be_ran_because_required_services_down": "Estos servicios necesarios deberían estar funcionando para ejecutar esta acción: {services}. Pruebe a reiniciarlos para continuar (y posiblemente investigar por qué están caídos).",
|
||||
"already_up_to_date": "Nada que hacer. Todo está actualizado.",
|
||||
"admin_password_too_long": "Elija una contraseña de menos de 127 caracteres",
|
||||
"aborting": "Cancelando.",
|
||||
"app_upgrade_stopped": "Se ha detenido la actualización de todas las aplicaciones para prevenir un posible daño porque una aplicación no se pudo actualizar",
|
||||
"app_action_broke_system": "Esta acción parece que ha roto estos importantes servicios: {services}",
|
||||
"operation_interrupted": "¿Ha sido interrumpida la operación manualmente?",
|
||||
"apps_already_up_to_date": "Todas las aplicaciones están ya actualizadas",
|
||||
"dyndns_provider_unreachable": "No se puede conectar con el proveedor de Dyndns {provider}: o su YunoHost no está correctamente conectado a Internet o el servidor de dynette está caído.",
|
||||
"group_already_exist": "El grupo {group} ya existe",
|
||||
"group_already_exist_on_system": "El grupo {group} ya existe en los grupos del sistema",
|
||||
"group_cannot_be_edited": "El grupo {group} no se puede editar manualmente.",
|
||||
"group_cannot_be_deleted": "El grupo {group} no se puede eliminar manualmente.",
|
||||
"group_user_already_in_group": "El usuario {user} ya está en el grupo {group}",
|
||||
"group_user_not_in_group": "El usuario {user} no está en el grupo {group}",
|
||||
"log_permission_create": "Crear permiso «{}»",
|
||||
"log_permission_delete": "Eliminar permiso «{}»",
|
||||
"log_permission_urls": "Actualizar URLs relacionadas con el permiso «{}»",
|
||||
"log_user_group_create": "Crear grupo «{}»",
|
||||
"log_user_permission_update": "Actualizar los accesos para el permiso «{}»",
|
||||
"log_user_permission_reset": "Restablecer permiso «{}»",
|
||||
"migration_0011_failed_to_remove_stale_object": "No se pudo eliminar el objeto obsoleto {dn}: {error}",
|
||||
"permission_already_allowed": "El grupo «{group}» ya tiene el permiso «{permission}» activado",
|
||||
"permission_already_disallowed": "El grupo «{group}» ya tiene el permiso «{permission}» desactivado",
|
||||
"permission_cannot_remove_main": "No está permitido eliminar un permiso principal",
|
||||
"user_already_exists": "El usuario «{user}» ya existe",
|
||||
"app_full_domain_unavailable": "Lamentablemente esta aplicación tiene que instalarse en un dominio propio pero ya hay otras aplicaciones instaladas en el dominio «{domain}». Podría usar un subdomino dedicado a esta aplicación en su lugar.",
|
||||
"app_install_failed": "No se pudo instalar {app}: {error}",
|
||||
"app_install_script_failed": "Ha ocurrido un error en el guión de instalación de la aplicación",
|
||||
"group_cannot_edit_all_users": "El grupo «all_users» no se puede editar manualmente. Es un grupo especial destinado a contener todos los usuarios registrados en YunoHost",
|
||||
"group_cannot_edit_visitors": "El grupo «visitors» no se puede editar manualmente. Es un grupo especial que representa a los visitantes anónimos",
|
||||
"group_cannot_edit_primary_group": "El grupo «{group}» no se puede editar manualmente. Es el grupo primario destinado a contener solo un usuario específico.",
|
||||
"log_permission_url": "Actualizar la URL relacionada con el permiso «{}»",
|
||||
"migration_0011_slapd_config_will_be_overwritten": "Parece que ha editado manualmente la configuración de slapd. Para esta migración crítica, YunoHost necesita forzar la actualización de la configuración de slapd. Los archivos originales se respaldarán en {conf_backup_folder}.",
|
||||
"permission_already_up_to_date": "El permiso no se ha actualizado porque las peticiones de incorporación o eliminación ya coinciden con el estado actual.",
|
||||
"permission_currently_allowed_for_visitors": "Este permiso se concede actualmente a los visitantes además de otros grupos. Probablemente quiere o eliminar el permiso de «visitors» o eliminar los otros grupos a los que está otorgado actualmente.",
|
||||
"permission_currently_allowed_for_all_users": "Este permiso se concede actualmente a todos los usuarios además de los otros grupos. Probablemente quiere o eliminar el permiso de «all_users» o eliminar los otros grupos a los que está otorgado actualmente.",
|
||||
"permission_require_account": "El permiso {permission} solo tiene sentido para usuarios con una cuenta y, por lo tanto, no se puede activar para visitantes.",
|
||||
"app_remove_after_failed_install": "Eliminando la aplicación tras el fallo de instalación…"
|
||||
}
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
{}
|
||||
{
|
||||
"password_too_simple_1": "Pasahitzak gutxienez 8 karaktere izan behar ditu"
|
||||
}
|
||||
|
|
370
locales/fr.json
370
locales/fr.json
|
@ -17,11 +17,11 @@
|
|||
"app_manifest_invalid": "Manifeste d’application incorrect : {error}",
|
||||
"app_no_upgrade": "Aucune application à mettre à jour",
|
||||
"app_not_correctly_installed": "{app:s} semble être mal installé",
|
||||
"app_not_installed": "{app:s} n’est pas installé",
|
||||
"app_not_installed": "Nous n’avons pas trouvé l’application « {app:s} » dans la liste des applications installées: {all_apps}",
|
||||
"app_not_properly_removed": "{app:s} n’a pas été supprimé correctement",
|
||||
"app_package_need_update": "Le paquet de l’application {app} doit être mis à jour pour être en adéquation avec les changements de YunoHost",
|
||||
"app_recent_version_required": "{app:s} nécessite une version plus récente de YunoHost",
|
||||
"app_removed": "{app:s} a été supprimé",
|
||||
"app_removed": "{app:s} supprimé",
|
||||
"app_requirements_checking": "Vérification des paquets requis pour {app} …",
|
||||
"app_requirements_failed": "Impossible de satisfaire les pré-requis pour {app} : {error}",
|
||||
"app_requirements_unmeet": "Les pré-requis de {app} ne sont pas satisfaits, le paquet {pkgname} ({version}) doit être {spec}",
|
||||
|
@ -29,11 +29,11 @@
|
|||
"app_unknown": "Application inconnue",
|
||||
"app_unsupported_remote_type": "Ce type de commande à distance utilisé pour cette application n'est pas supporté",
|
||||
"app_upgrade_failed": "Impossible de mettre à jour {app:s}",
|
||||
"app_upgraded": "{app:s} a été mis à jour",
|
||||
"appslist_fetched": "La liste d’applications {appslist:s} a été récupérée",
|
||||
"appslist_removed": "La liste d’applications {appslist:s} a été supprimée",
|
||||
"appslist_retrieve_error": "Impossible de récupérer la liste d’applications distante {appslist:s} : {error:s}",
|
||||
"appslist_unknown": "La liste d’applications {appslist:s} est inconnue.",
|
||||
"app_upgraded": "{app:s} mis à jour",
|
||||
"appslist_fetched": "La liste d’applications mise à jour '{appslist:s}'",
|
||||
"appslist_removed": "La liste d'applications '{appslist:s}' a été supprimée",
|
||||
"appslist_retrieve_error": "Impossible de récupérer la liste d’applications distante '{appslist:s}' : {error:s}",
|
||||
"appslist_unknown": "La liste d’applications '{appslist:s}' est inconnue.",
|
||||
"ask_current_admin_password": "Mot de passe d’administration actuel",
|
||||
"ask_email": "Adresse de courriel",
|
||||
"ask_firstname": "Prénom",
|
||||
|
@ -46,13 +46,13 @@
|
|||
"backup_app_failed": "Impossible de sauvegarder l’application '{app:s}'",
|
||||
"backup_archive_app_not_found": "L’application '{app:s}' n’a pas été trouvée dans l’archive de la sauvegarde",
|
||||
"backup_archive_hook_not_exec": "Le script « {hook:s} » n'a pas été exécuté dans cette sauvegarde",
|
||||
"backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà",
|
||||
"backup_archive_name_exists": "Une archive de sauvegarde avec ce nom existe déjà.",
|
||||
"backup_archive_name_unknown": "L’archive locale de sauvegarde nommée '{name:s}' est inconnue",
|
||||
"backup_archive_open_failed": "Impossible d’ouvrir l’archive de sauvegarde",
|
||||
"backup_archive_open_failed": "Impossible d’ouvrir l’archive de la sauvegarde",
|
||||
"backup_cleaning_failed": "Impossible de nettoyer le dossier temporaire de sauvegarde",
|
||||
"backup_created": "Sauvegarde terminée",
|
||||
"backup_creating_archive": "Création de l’archive de sauvegarde …",
|
||||
"backup_creation_failed": "Impossible de créer la sauvegarde",
|
||||
"backup_creation_failed": "Impossible de créer l'archive de la sauvegarde",
|
||||
"backup_delete_error": "Impossible de supprimer '{path:s}'",
|
||||
"backup_deleted": "La sauvegarde a été supprimée",
|
||||
"backup_extracting_archive": "Extraction de l’archive de sauvegarde …",
|
||||
|
@ -75,9 +75,9 @@
|
|||
"dnsmasq_isnt_installed": "dnsmasq ne semble pas être installé, veuillez lancer 'apt-get remove bind9 && apt-get install dnsmasq'",
|
||||
"domain_cert_gen_failed": "Impossible de générer le certificat",
|
||||
"domain_created": "Le domaine a été créé",
|
||||
"domain_creation_failed": "Impossible de créer le domaine",
|
||||
"domain_creation_failed": "Impossible de créer le domaine {domain}: {error}",
|
||||
"domain_deleted": "Le domaine a été supprimé",
|
||||
"domain_deletion_failed": "Impossible de supprimer le domaine",
|
||||
"domain_deletion_failed": "Impossible de supprimer le domaine {domain}: {error}",
|
||||
"domain_dyndns_already_subscribed": "Vous avez déjà souscris à un domaine DynDNS",
|
||||
"domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS",
|
||||
"domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu",
|
||||
|
@ -88,15 +88,15 @@
|
|||
"domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}",
|
||||
"done": "Terminé",
|
||||
"downloading": "Téléchargement en cours …",
|
||||
"dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été installée",
|
||||
"dyndns_cron_remove_failed": "Impossible de supprimer la tâche cron pour le domaine DynDNS",
|
||||
"dyndns_cron_removed": "La tâche cron pour le domaine DynDNS a été enlevée",
|
||||
"dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été créée",
|
||||
"dyndns_cron_remove_failed": "Impossible de supprimer la tâche cron DynDNS parce que: {error}",
|
||||
"dyndns_cron_removed": "La tâche cron pour le domaine DynDNS enlevée",
|
||||
"dyndns_ip_update_failed": "Impossible de mettre à jour l’adresse IP sur le domaine DynDNS",
|
||||
"dyndns_ip_updated": "Votre adresse IP a été mise à jour pour le domaine DynDNS",
|
||||
"dyndns_key_generating": "La clé DNS est en cours de génération, cela peut prendre un certain temps …",
|
||||
"dyndns_ip_updated": "Mise à jour de votre IP pour le domaine DynDNS",
|
||||
"dyndns_key_generating": "Génération de la clé DNS ... , cela peut prendre un certain temps.",
|
||||
"dyndns_key_not_found": "Clé DNS introuvable pour le domaine",
|
||||
"dyndns_no_domain_registered": "Aucun domaine n’a été enregistré avec DynDNS",
|
||||
"dyndns_registered": "Le domaine DynDNS a été enregistré",
|
||||
"dyndns_no_domain_registered": "Aucun domaine enregistré avec DynDNS",
|
||||
"dyndns_registered": "Domaine DynDNS enregistré",
|
||||
"dyndns_registration_failed": "Impossible d’enregistrer le domaine DynDNS : {error:s}",
|
||||
"dyndns_unavailable": "Le domaine {domain:s} est indisponible.",
|
||||
"executing_command": "Exécution de la commande '{command:s}' …",
|
||||
|
@ -104,8 +104,8 @@
|
|||
"extracting": "Extraction en cours …",
|
||||
"field_invalid": "Champ incorrect : '{:s}'",
|
||||
"firewall_reload_failed": "Impossible de recharger le pare-feu",
|
||||
"firewall_reloaded": "Le pare-feu a été rechargé",
|
||||
"firewall_rules_cmd_failed": "Certaines règles du pare-feu n’ont pas pu être appliquées. Pour plus d’informations, consultez le journal.",
|
||||
"firewall_reloaded": "Pare-feu rechargé",
|
||||
"firewall_rules_cmd_failed": "Certaines règles du pare-feu n’ont pas pu être appliquées. Plus d'info dans le journal de log.",
|
||||
"format_datetime_short": "%d/%m/%Y %H:%M",
|
||||
"hook_argument_missing": "Argument manquant : '{:s}'",
|
||||
"hook_choice_invalid": "Choix incorrect : '{:s}'",
|
||||
|
@ -114,28 +114,28 @@
|
|||
"hook_list_by_invalid": "Propriété invalide pour lister les actions par celle-ci",
|
||||
"hook_name_unknown": "Nom de l'action '{name:s}' inconnu",
|
||||
"installation_complete": "Installation terminée",
|
||||
"installation_failed": "Échec de l’installation",
|
||||
"installation_failed": "Quelque chose s'est mal passé lors de l'installation",
|
||||
"ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge",
|
||||
"iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le prend pas en charge",
|
||||
"ldap_initialized": "L’annuaire LDAP a été initialisé",
|
||||
"ldap_initialized": "L’annuaire LDAP initialisé",
|
||||
"license_undefined": "indéfinie",
|
||||
"mail_alias_remove_failed": "Impossible de supprimer l’alias de courriel '{mail:s}'",
|
||||
"mail_domain_unknown": "Le domaine d'adresse du courriel '{domain:s}' est inconnu",
|
||||
"mail_domain_unknown": "Le domaine '{domain:s}' de cette adress de courriel n'est pas valide. Merci d'utiliser un domain administré par ce serveur.",
|
||||
"mail_forward_remove_failed": "Impossible de supprimer le courriel de transfert '{mail:s}'",
|
||||
"maindomain_change_failed": "Impossible de modifier le domaine principal",
|
||||
"maindomain_changed": "Le domaine principal a été modifié",
|
||||
"monitor_disabled": "La supervision du serveur a été désactivé",
|
||||
"monitor_enabled": "La supervision du serveur a été activé",
|
||||
"main_domain_change_failed": "Impossible de modifier le domaine principal",
|
||||
"main_domain_changed": "Le domaine principal modifié",
|
||||
"monitor_disabled": "Surveillance du serveur est maintenant arrêté",
|
||||
"monitor_enabled": "La supervision du serveur est maintenant allumée",
|
||||
"monitor_glances_con_failed": "Impossible de se connecter au serveur Glances",
|
||||
"monitor_not_enabled": "Le suivi de l’état du serveur n’est pas activé",
|
||||
"monitor_period_invalid": "Période de temps incorrecte",
|
||||
"monitor_stats_file_not_found": "Le fichier de statistiques est introuvable",
|
||||
"monitor_stats_file_not_found": "Impossible de trouver le fichier de statistiques",
|
||||
"monitor_stats_no_update": "Aucune donnée de l’état du serveur à mettre à jour",
|
||||
"monitor_stats_period_unavailable": "Aucune statistique n’est disponible pour la période",
|
||||
"mountpoint_unknown": "Point de montage inconnu",
|
||||
"mysql_db_creation_failed": "Impossible de créer la base de données MySQL",
|
||||
"mysql_db_init_failed": "Impossible d’initialiser la base de données MySQL",
|
||||
"mysql_db_initialized": "La base de données MySQL a été initialisée",
|
||||
"mysql_db_init_failed": "Impossible d'initialiser la base de données MySQL",
|
||||
"mysql_db_initialized": "La base de données MySQL est maintenant initialisée",
|
||||
"network_check_mx_ko": "L’enregistrement DNS MX n’est pas défini",
|
||||
"network_check_smtp_ko": "Le trafic courriel sortant (port 25 SMTP) semble bloqué par votre réseau",
|
||||
"network_check_smtp_ok": "Le trafic courriel sortant (port 25 SMTP) n’est pas bloqué",
|
||||
|
@ -155,7 +155,7 @@
|
|||
"path_removal_failed": "Impossible de supprimer le chemin {:s}",
|
||||
"pattern_backup_archive_name": "Doit être un nom de fichier valide avec un maximum de 30 caractères, et composé de caractères alphanumériques et -_. uniquement",
|
||||
"pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.fr)",
|
||||
"pattern_email": "Doit être une adresse de courriel valide (ex. : pseudo@domaine.fr)",
|
||||
"pattern_email": "Doit être une adresse de courriel valide (ex. : pseudo@example.com)",
|
||||
"pattern_firstname": "Doit être un prénom valide",
|
||||
"pattern_lastname": "Doit être un nom valide",
|
||||
"pattern_listname": "Doit être composé uniquement de caractères alphanumériques et de tirets bas (aussi appelé tiret du 8 ou underscore)",
|
||||
|
@ -173,7 +173,7 @@
|
|||
"restore_already_installed_app": "Une application est déjà installée avec l’identifiant '{app:s}'",
|
||||
"restore_app_failed": "Impossible de restaurer l’application '{app:s}'",
|
||||
"restore_cleaning_failed": "Impossible de nettoyer le dossier temporaire de restauration",
|
||||
"restore_complete": "Restauration terminée",
|
||||
"restore_complete": "Restauré",
|
||||
"restore_confirm_yunohost_installed": "Voulez-vous vraiment restaurer un système déjà installé ? [{answers:s}]",
|
||||
"restore_failed": "Impossible de restaurer le système",
|
||||
"restore_hook_unavailable": "Le script de restauration '{part:s}' n’est pas disponible sur votre système, et ne l'est pas non plus dans l’archive",
|
||||
|
@ -182,8 +182,8 @@
|
|||
"restore_running_hooks": "Exécution des scripts de restauration …",
|
||||
"service_add_configuration": "Ajout du fichier de configuration {file:s}",
|
||||
"service_add_failed": "Impossible d’ajouter le service '{service:s}'",
|
||||
"service_added": "Le service '{service:s}' a été ajouté",
|
||||
"service_already_started": "Le service '{service:s}' est déjà démarré",
|
||||
"service_added": "Le service '{service:s}' ajouté",
|
||||
"service_already_started": "Le service '{service:s}' est déjà en cours d'exécution",
|
||||
"service_already_stopped": "Le service '{service:s}' est déjà arrêté",
|
||||
"service_cmd_exec_failed": "Impossible d’exécuter la commande '{command:s}'",
|
||||
"service_conf_file_backed_up": "Le fichier de configuration '{conf}' a été sauvegardé dans '{backup}'",
|
||||
|
@ -204,7 +204,7 @@
|
|||
"service_disabled": "Le service '{service:s}' a été désactivé",
|
||||
"service_enable_failed": "Impossible d’activer le service '{service:s}'\n\nJournaux historisés récents : {logs:s}",
|
||||
"service_enabled": "Le service '{service:s}' a été activé",
|
||||
"service_no_log": "Aucun journal historisé à afficher pour le service '{service:s}'",
|
||||
"service_no_log": "Aucun journal à afficher pour le service '{service:s}'",
|
||||
"service_regenconf_dry_pending_applying": "Vérification des configurations en attentes qui pourraient être appliquées au le service '{service}' …",
|
||||
"service_regenconf_failed": "Impossible de régénérer la configuration pour les services : {services}",
|
||||
"service_regenconf_pending_applying": "Application des configurations en attentes pour le service '{service}' …",
|
||||
|
@ -218,9 +218,9 @@
|
|||
"service_unknown": "Le service '{service:s}' est inconnu",
|
||||
"services_configured": "La configuration a été générée avec succès",
|
||||
"show_diff": "Voici les différences :\n{diff:s}",
|
||||
"ssowat_conf_generated": "La configuration de SSOwat a été générée",
|
||||
"ssowat_conf_updated": "La configuration de SSOwat a été mise à jour",
|
||||
"system_upgraded": "Le système a été mis à jour",
|
||||
"ssowat_conf_generated": "La configuration de SSOwat générée",
|
||||
"ssowat_conf_updated": "La configuration de SSOwat mise à jour",
|
||||
"system_upgraded": "Système mis à jour",
|
||||
"system_username_exists": "Ce nom d’utilisateur existe déjà dans les utilisateurs système",
|
||||
"unbackup_app": "L’application '{app:s}' ne sera pas sauvegardée",
|
||||
"unexpected_error": "Une erreur inattendue est survenue : {error}",
|
||||
|
@ -232,23 +232,23 @@
|
|||
"upgrade_complete": "Mise à jour terminée",
|
||||
"upgrading_packages": "Mise à jour des paquets en cours …",
|
||||
"upnp_dev_not_found": "Aucun périphérique compatible UPnP n’a été trouvé",
|
||||
"upnp_disabled": "UPnP a été désactivé",
|
||||
"upnp_enabled": "UPnP a été activé",
|
||||
"upnp_disabled": "UPnP désactivé",
|
||||
"upnp_enabled": "UPnP activé",
|
||||
"upnp_port_open_failed": "Impossible d’ouvrir les ports UPnP",
|
||||
"user_created": "L’utilisateur a été créé",
|
||||
"user_creation_failed": "Impossible de créer l’utilisateur",
|
||||
"user_deleted": "L’utilisateur a été supprimé",
|
||||
"user_deletion_failed": "Impossible de supprimer l’utilisateur",
|
||||
"user_created": "L’utilisateur créé",
|
||||
"user_creation_failed": "Impossible de créer l’utilisateur {user}: {error}",
|
||||
"user_deleted": "L’utilisateur supprimé",
|
||||
"user_deletion_failed": "Impossible de supprimer l’utilisateur {user}: {error}",
|
||||
"user_home_creation_failed": "Impossible de créer le dossier personnel de l’utilisateur",
|
||||
"user_info_failed": "Impossible de récupérer les informations de l’utilisateur",
|
||||
"user_unknown": "L'utilisateur {user:s} est inconnu",
|
||||
"user_update_failed": "Impossible de modifier l’utilisateur",
|
||||
"user_update_failed": "Impossible de mettre à jour l'utilisateur {utilisateur}: {erreur}",
|
||||
"user_updated": "L’utilisateur a été modifié",
|
||||
"yunohost_already_installed": "YunoHost est déjà installé",
|
||||
"yunohost_ca_creation_failed": "Impossible de créer l’autorité de certification",
|
||||
"yunohost_configured": "YunoHost a été configuré",
|
||||
"yunohost_configured": "YunoHost est maintenant configuré",
|
||||
"yunohost_installing": "L'installation de YunoHost est en cours …",
|
||||
"yunohost_not_installed": "YunoHost n’est pas ou pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'",
|
||||
"yunohost_not_installed": "YunoHost n'est pas correctement installé. Veuillez exécuter 'yunohost tools postinstall'",
|
||||
"certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de vouloir remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner cela)",
|
||||
"certmanager_domain_unknown": "Domaine {domain:s} inconnu",
|
||||
"certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} n’est pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force pour cela)",
|
||||
|
@ -259,17 +259,17 @@
|
|||
"certmanager_error_no_A_record": "Aucun enregistrement DNS 'A' n’a été trouvé pour {domain:s}. Vous devez faire pointer votre nom de domaine vers votre machine pour être en mesure d’installer un certificat Let’s Encrypt ! (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)",
|
||||
"certmanager_domain_dns_ip_differs_from_public_ip": "L’enregistrement DNS 'A' du domaine {domain:s} est différent de l’adresse IP de ce serveur. Si vous avez récemment modifié votre enregistrement 'A', veuillez attendre sa propagation (quelques vérificateur de propagation DNS sont disponibles en ligne). (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces contrôles)",
|
||||
"certmanager_cannot_read_cert": "Quelque chose s’est mal passé lors de la tentative d’ouverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), la cause est : {reason:s}",
|
||||
"certmanager_cert_install_success_selfsigned": "Installation avec succès d’un certificat auto-signé pour le domaine {domain:s} !",
|
||||
"certmanager_cert_install_success": "Installation avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !",
|
||||
"certmanager_cert_renew_success": "Renouvellement avec succès d’un certificat Let’s Encrypt pour le domaine {domain:s} !",
|
||||
"certmanager_cert_install_success_selfsigned": "Le certificat auto-signé est maintenant installé pour le domaine « {domain:s} »",
|
||||
"certmanager_cert_install_success": "Le certificat Let’s Encrypt est maintenant installé pour le domaine « {domain:s} »",
|
||||
"certmanager_cert_renew_success": "Certificat Let's Encrypt renouvelé pour le domaine '{domain: s}'",
|
||||
"certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que l’application « letsencrypt » est installé, ce qui est en conflit avec les nouvelles fonctionnalités de gestion intégrée de certificats dans YunoHost. Si vous souhaitez utiliser ces nouvelles fonctionnalités intégrées, veuillez lancer les commandes suivantes pour migrer votre installation :\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B. : cela tentera de réinstaller les certificats de tous les domaines avec un certificat Let's Encrypt ou ceux auto-signés",
|
||||
"certmanager_cert_signing_failed": "La signature du nouveau certificat a échoué",
|
||||
"certmanager_cert_signing_failed": "Impossible de signer le nouveau certificat",
|
||||
"certmanager_no_cert_file": "Impossible de lire le fichier du certificat pour le domaine {domain:s} (fichier : {file:s})",
|
||||
"certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration Nginx {filepath:s} est en conflit et doit être préalablement retiré",
|
||||
"certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour le défi ACME : le fichier de configuration NGINX {filepath:s} est en conflit et doit être préalablement retiré",
|
||||
"certmanager_hit_rate_limit": "Trop de certificats ont déjà été émis récemment pour ce même ensemble de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails sur les ratios et limitations",
|
||||
"ldap_init_failed_to_create_admin": "L’initialisation de l'annuaire LDAP n’a pas réussi à créer l’utilisateur admin",
|
||||
"ssowat_persistent_conf_read_error": "Erreur lors de la lecture de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON",
|
||||
"ssowat_persistent_conf_write_error": "Erreur lors de la sauvegarde de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON",
|
||||
"ssowat_persistent_conf_read_error": "Impossible de lire la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON",
|
||||
"ssowat_persistent_conf_write_error": "Impossible de sauvegarder de la configuration persistante de SSOwat : {error:s}. Modifiez le fichier /etc/ssowat/conf.json.persistent pour réparer la syntaxe JSON",
|
||||
"domain_cannot_remove_main": "Impossible de supprimer le domaine principal. Définissez d'abord un nouveau domaine principal",
|
||||
"certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour l’autorité du certificat auto-signé est introuvable (fichier : {file:s})",
|
||||
"certmanager_unable_to_parse_self_CA_name": "Impossible d’analyser le nom de l’autorité du certificat auto-signé (fichier : {file:s})",
|
||||
|
@ -280,14 +280,14 @@
|
|||
"certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être résolu depuis votre serveur YunoHost. Cela peut se produire si vous avez récemment modifié votre enregistrement DNS. Si c'est le cas, merci d’attendre quelques heures qu’il se propage. Si le problème persiste, envisager d’ajouter {domain:s} au fichier /etc/hosts. (Si vous savez ce que vous faites, utilisez --no-checks pour désactiver ces vérifications.)",
|
||||
"certmanager_http_check_timeout": "Expiration du délai lorsque le serveur a essayé de se contacter lui-même via HTTP en utilisant l'adresse IP public {ip:s} du domaine {domain:s}. Vous rencontrez peut-être un problème d’hairpinning ou alors le pare-feu/routeur en amont de votre serveur est mal configuré.",
|
||||
"certmanager_couldnt_fetch_intermediate_cert": "Expiration du délai lors de la tentative de récupération du certificat intermédiaire depuis Let’s Encrypt. L’installation ou le renouvellement du certificat a été annulé. Veuillez réessayer plus tard.",
|
||||
"appslist_retrieve_bad_format": "Le fichier récupéré pour la liste d’applications {appslist:s} n’est pas valide",
|
||||
"domain_hostname_failed": "Échec de la création d’un nouveau nom d’hôte",
|
||||
"yunohost_ca_creation_success": "L’autorité de certification locale a été créée.",
|
||||
"appslist_name_already_tracked": "Il y a déjà une liste d’applications enregistrée avec le nom {name:s}.",
|
||||
"appslist_retrieve_bad_format": "Impossible de lire la liste des applications extraites '{appslist: s}'",
|
||||
"domain_hostname_failed": "Échec de l’utilisation d’un nouveau nom d’hôte. Cela pourrait causer des soucis plus tard (peut-être que ça n’en causera pas).",
|
||||
"yunohost_ca_creation_success": "L’autorité de certification locale créée.",
|
||||
"appslist_name_already_tracked": "Une liste d'applications enregistrées portant le nom {name:s} existe déjà.",
|
||||
"appslist_url_already_tracked": "Il y a déjà une liste d’applications enregistrée avec l’URL {url:s}.",
|
||||
"appslist_migrating": "Migration de la liste d’applications {appslist:s} …",
|
||||
"appslist_could_not_migrate": "Impossible de migrer la liste {appslist:s} ! Impossible d’exploiter l’URL. L’ancienne tâche programmée a été conservée dans {bkp_file:s}.",
|
||||
"appslist_corrupted_json": "Impossible de charger la liste d’applications. Il semble que {filename:s} soit corrompu.",
|
||||
"appslist_migrating": "Migration de la liste d’applications '{appslist:s}' …",
|
||||
"appslist_could_not_migrate": "Impossible de migrer la liste '{appslist:s}' ! Impossible d’exploiter l’URL. L’ancienne tâche programmée a été conservée dans {bkp_file:s}.",
|
||||
"appslist_corrupted_json": "Impossible de charger la liste d’applications. Il semble que {filename:s} soit endommager.",
|
||||
"app_already_installed_cant_change_url": "Cette application est déjà installée. L’URL ne peut pas être changé simplement par cette fonction. Regardez si cela est disponible avec `app changeurl`.",
|
||||
"app_change_no_change_url_script": "L’application {app_name:s} ne prend pas encore en charge le changement d’URL, vous pourriez avoir besoin de la mettre à jour.",
|
||||
"app_change_url_failed_nginx_reload": "Le redémarrage de Nginx a échoué. Voici la sortie de 'nginx -t' :\n{nginx_errors:s}",
|
||||
|
@ -297,13 +297,13 @@
|
|||
"app_location_unavailable": "Cette URL n’est pas disponible ou est en conflit avec une application existante :\n{apps:s}",
|
||||
"app_already_up_to_date": "{app:s} est déjà à jour",
|
||||
"invalid_url_format": "Format d’URL non valide",
|
||||
"global_settings_bad_choice_for_enum": "Valeur du paramètre {setting:s} incorrecte. Reçu : {received_type:s}, mais les valeurs possibles sont : {expected_type:s}",
|
||||
"global_settings_bad_choice_for_enum": "Valeur du paramètre {setting:s} incorrecte. Reçu : {choice:s}, mais les valeurs possibles sont : {available_choices:s}",
|
||||
"global_settings_bad_type_for_setting": "Le type du paramètre {setting:s} est incorrect. Reçu {received_type:s} alors que {expected_type:s} était attendu",
|
||||
"global_settings_cant_open_settings": "Échec de l’ouverture du ficher de configurations car : {reason:s}",
|
||||
"global_settings_cant_serialize_setings": "Échec de sérialisation des données de configurations, cause : {reason:s}",
|
||||
"global_settings_cant_write_settings": "Échec d’écriture du fichier de configurations car : {reason:s}",
|
||||
"global_settings_key_doesnt_exists": "La clef '{settings_key:s}' n’existe pas dans les configurations générales, vous pouvez voir toutes les clefs disponibles en saisissant 'yunohost settings list'",
|
||||
"global_settings_reset_success": "Super ! Vos configurations précédentes ont été sauvegardées dans {path:s}",
|
||||
"global_settings_reset_success": "Vos configurations précédentes ont été sauvegardées dans {path:s}",
|
||||
"global_settings_setting_example_bool": "Exemple d’option booléenne",
|
||||
"global_settings_setting_example_int": "Exemple d’option de type entier",
|
||||
"global_settings_setting_example_string": "Exemple d’option de type chaîne",
|
||||
|
@ -312,32 +312,32 @@
|
|||
"global_settings_unknown_setting_from_settings_file": "Clé inconnue dans les paramètres : '{setting_key:s}', rejet de cette clé et sauvegarde de celle-ci dans /etc/yunohost/unkown_settings.json",
|
||||
"service_conf_new_managed_file": "Le fichier de configuration « {conf} » est désormais géré par le service {service}.",
|
||||
"service_conf_file_kept_back": "Le fichier de configuration '{conf}' devait être supprimé par le service {service} mais a été conservé.",
|
||||
"backup_abstract_method": "Cette méthode de sauvegarde n’a pas encore été implémentée",
|
||||
"backup_applying_method_tar": "Création de l’archive tar de la sauvegarde …",
|
||||
"backup_abstract_method": "Cette méthode de sauvegarde reste à implémenter",
|
||||
"backup_applying_method_tar": "Création de l’archive TAR de la sauvegarde…",
|
||||
"backup_applying_method_copy": "Copie de tous les fichiers à sauvegarder …",
|
||||
"backup_applying_method_borg": "Envoi de tous les fichiers à sauvegarder dans le répertoire borg-backup…",
|
||||
"backup_applying_method_custom": "Appel de la méthode de sauvegarde personnalisée '{method:s}' …",
|
||||
"backup_archive_system_part_not_available": "La partie '{part:s}' du système n’est pas disponible dans cette sauvegarde",
|
||||
"backup_archive_mount_failed": "Le montage de l’archive de sauvegarde a échoué",
|
||||
"backup_archive_writing_error": "Impossible d'ajouter des fichiers '{source:s}' (nommés dans l'archive : '{dest:s}') à sauvegarder dans l'archive compressée '{archive:s}'",
|
||||
"backup_ask_for_copying_if_needed": "Certains fichiers n’ont pas pu être préparés pour être sauvegardés en utilisant la méthode qui évite temporairement de gaspiller de l’espace sur le système. Pour réaliser la sauvegarde, {size:s} Mo doivent être temporairement utilisés. Acceptez-vous ?",
|
||||
"backup_ask_for_copying_if_needed": "Voulez-vous effectuer la sauvegarde en utilisant {taille:s} temporairement? (Cette méthode est utilisée car certains fichiers n'ont pas pu être préparés avec une méthode plus efficace.)",
|
||||
"backup_borg_not_implemented": "La méthode de sauvegarde Borg n’est pas encore implémentée",
|
||||
"backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée",
|
||||
"backup_copying_to_organize_the_archive": "Copie de {size:s} Mo pour organiser l’archive",
|
||||
"backup_csv_creation_failed": "Impossible de créer le fichier CSV nécessaire aux opérations futures de restauration",
|
||||
"backup_csv_creation_failed": "Impossible de créer le fichier CSV nécessaire à la restauration",
|
||||
"backup_csv_addition_failed": "Impossible d’ajouter des fichiers à sauvegarder dans le fichier CSV",
|
||||
"backup_custom_need_mount_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'need_mount'",
|
||||
"backup_custom_backup_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'backup'",
|
||||
"backup_custom_mount_error": "Échec de la méthode de sauvegarde personnalisée à l’étape 'mount'",
|
||||
"backup_no_uncompress_archive_dir": "Le dossier de l’archive décompressée n’existe pas",
|
||||
"backup_method_tar_finished": "L’archive tar de la sauvegarde a été créée",
|
||||
"backup_no_uncompress_archive_dir": "Ce dossier d’archive décompressée n’existe pas",
|
||||
"backup_method_tar_finished": "L’archive TAR de la sauvegarde a été créée",
|
||||
"backup_method_copy_finished": "La copie de la sauvegarde est terminée",
|
||||
"backup_method_borg_finished": "La sauvegarde dans Borg est terminée",
|
||||
"backup_method_custom_finished": "La méthode de sauvegarde personnalisée '{method:s}' est terminée",
|
||||
"backup_system_part_failed": "Impossible de sauvegarder la partie '{part:s}' du système",
|
||||
"backup_unable_to_organize_files": "Impossible d’organiser les fichiers dans l’archive avec la méthode rapide",
|
||||
"backup_unable_to_organize_files": "Impossible d’utiliser la méthode rapide pour organiser les fichiers dans l’archive",
|
||||
"backup_with_no_backup_script_for_app": "L’application {app:s} n’a pas de script de sauvegarde. Ignorer.",
|
||||
"backup_with_no_restore_script_for_app": "L’application {app:s} n’a pas de script de restauration, vous ne pourrez pas restaurer automatiquement la sauvegarde de cette application.",
|
||||
"backup_with_no_restore_script_for_app": "L’application « {app:s} » n’a pas de script de restauration, vous ne pourrez pas restaurer automatiquement la sauvegarde de cette application.",
|
||||
"global_settings_cant_serialize_settings": "Échec de la sérialisation des données de paramétrage car : {reason:s}",
|
||||
"restore_removing_tmp_dir_failed": "Impossible de sauvegarder un ancien dossier temporaire",
|
||||
"restore_extracting": "Extraction des fichiers nécessaires depuis l’archive …",
|
||||
|
@ -350,35 +350,35 @@
|
|||
"domain_dyndns_dynette_is_unreachable": "Impossible de contacter la dynette YunoHost. Soit YunoHost n’est pas correctement connecté à internet, soit le serveur de dynette est en panne. Erreur : {error}",
|
||||
"migrations_backward": "Migration en arrière.",
|
||||
"migrations_bad_value_for_target": "Nombre invalide pour le paramètre target, les numéros de migration sont 0 ou {}",
|
||||
"migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migrations avec le chemin %s",
|
||||
"migrations_cant_reach_migration_file": "Impossible d'accéder aux fichiers de migration via le chemin '%s'",
|
||||
"migrations_current_target": "La cible de migration est {}",
|
||||
"migrations_error_failed_to_load_migration": "ERREUR : échec du chargement de migration {number} {name}",
|
||||
"migrations_forward": "Migration en avant",
|
||||
"migrations_loading_migration": "Chargement de la migration {number} {name} …",
|
||||
"migrations_migration_has_failed": "La migration {number} {name} a échoué avec l’exception {exception} : annulation",
|
||||
"migrations_loading_migration": "Chargement de la migration {id} …",
|
||||
"migrations_migration_has_failed": "La migration {id} a échoué avec l’exception {exception} : annulation",
|
||||
"migrations_no_migrations_to_run": "Aucune migration à lancer",
|
||||
"migrations_show_currently_running_migration": "Application de la migration {number} {name} …",
|
||||
"migrations_show_last_migration": "La dernière migration appliquée est {}",
|
||||
"migrations_skip_migration": "Ignorer et passer la migration {number} {name}…",
|
||||
"migrations_skip_migration": "Ignorer et passer la migration {id}…",
|
||||
"server_shutdown": "Le serveur va éteindre",
|
||||
"server_shutdown_confirm": "Le serveur va être éteint immédiatement, le voulez-vous vraiment ? [{answers:s}]",
|
||||
"server_reboot": "Le serveur va redémarrer",
|
||||
"server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers:s}]",
|
||||
"app_upgrade_some_app_failed": "Impossible de mettre à jour certaines applications",
|
||||
"app_upgrade_some_app_failed": "Certaines applications n’ont pas été mises à jour",
|
||||
"ask_path": "Chemin",
|
||||
"dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.",
|
||||
"dyndns_domain_not_provided": "Le fournisseur DynDNS {provider:s} ne peut pas fournir le domaine {domain:s}.",
|
||||
"app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine {domain} car il est déjà utilisé par l'application '{other_app}'",
|
||||
"app_make_default_location_already_used": "Impossible de configurer l’application '{app}' par défaut pour le domaine '{domain}' car il est déjà utilisé par l'application '{other_app}'",
|
||||
"app_upgrade_app_name": "Mise à jour de l’application {app} …",
|
||||
"backup_output_symlink_dir_broken": "Vous avez un lien symbolique cassé à la place de votre dossier d’archives « {path:s} ». Vous pourriez avoir une configuration personnalisée pour sauvegarder vos données sur un autre système de fichiers, dans ce cas, vous avez probablement oublié de monter ou de connecter votre disque dur ou votre clé USB.",
|
||||
"migrate_tsig_end": "La migration à hmac-sha512 est terminée",
|
||||
"backup_output_symlink_dir_broken": "Votre répertoire d'archivage '{path:s}' est un lien symbolique brisé. Peut-être avez-vous oublié de re/monter ou de brancher le support de stockage sur lequel il pointe.",
|
||||
"migrate_tsig_end": "La migration à HMAC-SHA-512 est terminée",
|
||||
"migrate_tsig_failed": "La migration du domaine DynDNS {domain} à hmac-sha512 a échoué. Annulation des modifications. Erreur : {error_code} - {error}",
|
||||
"migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers hmac-sha512 qui est plus sécurisé",
|
||||
"migrate_tsig_wait": "Attendre 3 minutes pour que le serveur DynDNS prenne en compte la nouvelle clef …",
|
||||
"migrate_tsig_start": "L’algorithme de génération des clefs n’est pas suffisamment sécurisé pour la signature TSIG du domaine '{domain}', lancement de la migration vers HMAC-SHA-512 qui est plus sécurisé",
|
||||
"migrate_tsig_wait": "Attendre trois minutes pour que le serveur DynDNS prenne en compte la nouvelle clef …",
|
||||
"migrate_tsig_wait_2": "2 minutes …",
|
||||
"migrate_tsig_wait_3": "1 minute …",
|
||||
"migrate_tsig_wait_4": "30 secondes …",
|
||||
"migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine DynDNS, donc aucune migration n’est nécessaire !",
|
||||
"migrate_tsig_not_needed": "Il ne semble pas que vous utilisez un domaine DynDNS, donc aucune migration n’est nécessaire.",
|
||||
"app_checkurl_is_deprecated": "Packagers /!\\ 'app checkurl' est obsolète ! Utilisez 'app register-url' en remplacement !",
|
||||
"migration_description_0001_change_cert_group_to_sslcert": "Changement des permissions de groupe des certificats de « metronome » à « ssl-cert »",
|
||||
"migration_description_0002_migrate_to_tsig_sha256": "Amélioration de la sécurité de DynDNS TSIG en utilisant SHA512 au lieu de MD5",
|
||||
|
@ -393,37 +393,37 @@
|
|||
"migration_0003_not_jessie": "La distribution Debian actuelle n’est pas Jessie !",
|
||||
"migration_0003_system_not_fully_up_to_date": "Votre système n’est pas complètement à jour. Veuillez mener une mise à jour classique avant de lancer à migration à Stretch.",
|
||||
"migration_0003_still_on_jessie_after_main_upgrade": "Quelque chose s’est mal passé pendant la mise à niveau principale : le système est toujours sur Debian Jessie !? Pour investiguer sur le problème, veuillez regarder les journaux {log}:s …",
|
||||
"migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence !",
|
||||
"migration_0003_general_warning": "Veuillez noter que cette migration est une opération délicate. Si l’équipe YunoHost a fait de son mieux pour la relire et la tester, la migration pourrait tout de même casser des parties de votre système ou de vos applications.\n\nEn conséquence, nous vous recommandons :\n - de lancer une sauvegarde de vos données ou applications critiques. Plus d’informations sur https://yunohost.org/backup ;\n - d’être patient après avoir lancé la migration : selon votre connexion internet et matériel, cela pourrait prendre jusqu’à quelques heures pour que tout soit à niveau.\n\nEn outre, le port SMTP utilisé par les clients de messagerie externes comme (Thunderbird ou K9-Mail) a été changé de 465 (SSL/TLS) à 587 (STARTTLS). L’ancien port 465 sera automatiquement fermé et le nouveau port 587 sera ouvert dans le pare-feu. Vous et vos utilisateurs *devront* adapter la configuration de vos clients de messagerie en conséquence.",
|
||||
"migration_0003_problematic_apps_warning": "Veuillez noter que des applications possiblement problématiques ont été détectées. Il semble qu’elles n’aient pas été installées depuis une liste d’application ou qu’elles ne soit pas marquées comme « fonctionnelles ». En conséquence, nous ne pouvons pas garantir qu’elles fonctionneront après la mise à niveau : {problematic_apps}",
|
||||
"migration_0003_modified_files": "Veuillez noter que les fichiers suivants ont été détectés comme modifiés manuellement et pourraient être écrasés à la fin de la mise à niveau : {manually_modified_files}",
|
||||
"migrations_list_conflict_pending_done": "Vous ne pouvez pas utiliser --previous et --done simultanément.",
|
||||
"migrations_to_be_ran_manually": "La migration {number} {name} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.",
|
||||
"migrations_need_to_accept_disclaimer": "Pour lancer la migration {number} {name}, vous devez accepter cette clause de non-responsabilité :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.",
|
||||
"service_description_avahi-daemon": "permet d’atteindre votre serveur via yunohost.local sur votre réseau local",
|
||||
"service_description_dnsmasq": "gère la résolution des noms de domaine (DNS)",
|
||||
"service_description_dovecot": "permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)",
|
||||
"service_description_fail2ban": "protège contre les attaques brute-force et autres types d’attaques venant d’Internet",
|
||||
"service_description_glances": "surveille les informations système de votre serveur",
|
||||
"service_description_metronome": "gère les comptes de messagerie instantanée XMPP",
|
||||
"service_description_mysql": "stocke les données des applications (bases de données SQL)",
|
||||
"service_description_nginx": "sert ou permet l’accès à tous les sites web hébergés sur votre serveur",
|
||||
"service_description_nslcd": "gère la connexion en ligne de commande des utilisateurs YunoHost",
|
||||
"migrations_to_be_ran_manually": "La migration {id} doit être lancée manuellement. Veuillez aller dans Outils > Migrations dans l’interface admin, ou lancer `yunohost tools migrations migrate`.",
|
||||
"migrations_need_to_accept_disclaimer": "Pour lancer la migration {id}, vous devez accepter cette clause de non-responsabilité :\n---\n{disclaimer}\n---\nSi vous acceptez de lancer la migration, veuillez relancer la commande avec l’option --accept-disclaimer.",
|
||||
"service_description_avahi-daemon": "Vous permet d’atteindre votre serveur en utilisant «yunohost.local» sur votre réseau local",
|
||||
"service_description_dnsmasq": "Gère la résolution des noms de domaine (DNS)",
|
||||
"service_description_dovecot": "Permet aux clients de messagerie d’accéder/récupérer les courriels (via IMAP et POP3)",
|
||||
"service_description_fail2ban": "Protège contre les attaques brute-force et autres types d’attaques venant d’Internet",
|
||||
"service_description_glances": "Surveille les info système de votre serveur",
|
||||
"service_description_metronome": "Gère les comptes de messagerie instantanée XMPP",
|
||||
"service_description_mysql": "Stocke les données des applications (bases de données SQL)",
|
||||
"service_description_nginx": "Sert ou permet l’accès à tous les sites web hébergés sur votre serveur",
|
||||
"service_description_nslcd": "Gère la connexion en ligne de commande des utilisateurs YunoHost",
|
||||
"service_description_php5-fpm": "exécute des applications écrites en PHP avec nginx",
|
||||
"service_description_postfix": "utilisé pour envoyer et recevoir des courriels",
|
||||
"service_description_redis-server": "une base de données spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication entre les programmes",
|
||||
"service_description_rmilter": "vérifie divers paramètres dans les courriels",
|
||||
"service_description_rspamd": "filtre le pourriel, et d’autres fonctionnalités liées au courriel",
|
||||
"service_description_slapd": "stocke les utilisateurs, domaines et leurs informations liées",
|
||||
"service_description_ssh": "vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)",
|
||||
"service_description_yunohost-api": "permet les interactions entre l’interface web de YunoHost et le système",
|
||||
"service_description_yunohost-firewall": "gère l'ouverture et la fermeture des ports de connexion aux services",
|
||||
"service_description_postfix": "Utilisé pour envoyer et recevoir des courriels",
|
||||
"service_description_redis-server": "Une base de données spécialisée utilisée pour l’accès rapide aux données, les files d’attentes et la communication entre les programmes",
|
||||
"service_description_rmilter": "Vérifie divers paramètres dans les courriels",
|
||||
"service_description_rspamd": "Filtre le pourriel, et d’autres fonctionnalités liées au courriel",
|
||||
"service_description_slapd": "Stocke les utilisateurs, domaines et leurs informations liées",
|
||||
"service_description_ssh": "Vous permet de vous connecter à distance à votre serveur via un terminal (protocole SSH)",
|
||||
"service_description_yunohost-api": "Permet les interactions entre l’interface web de YunoHost et le système",
|
||||
"service_description_yunohost-firewall": "Gère l'ouverture et la fermeture des ports de connexion aux services",
|
||||
"experimental_feature": "Attention : cette fonctionnalité est expérimentale et ne doit pas être considérée comme stable, vous ne devriez pas l’utiliser à moins que vous ne sachiez ce que vous faites.",
|
||||
"log_corrupted_md_file": "Le fichier yaml de metadata associé aux logs est corrompu : '{md_file}'",
|
||||
"log_corrupted_md_file": "Le fichier YAML de métadonnées associé aux logs est corrompu : '{md_file}'\nErreur : {error}",
|
||||
"log_category_404": "Le journal de la catégorie '{category}' n’existe pas",
|
||||
"log_link_to_log": "Journal historisé complet de cette opération : '<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\"> {desc} </a>'",
|
||||
"log_help_to_get_log": "Pour voir le journal historisé de cette opération '{desc}', utilisez la commande 'yunohost log display {name}'",
|
||||
"log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci <a href=\"#/tools/logs/{name}\"> de fournir le journal historisé complet de l’opération en cliquant ici</a>",
|
||||
"backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge php7, vos applications php pourraient ne pas être restaurées (reason: {error:s})",
|
||||
"log_link_to_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci <a href=\"#/tools/logs/{name}\"> cliqué ici</a> pour avoir de l'aide",
|
||||
"backup_php5_to_php7_migration_may_fail": "Impossible de convertir votre archive pour prendre en charge PHP 7, vous pourriez ne plus pouvoir restaurer vos applications PHP (cause : {error:s})",
|
||||
"log_help_to_get_failed_log": "L’opération '{desc}' a échouée ! Pour avoir de l’aide, merci de partager le journal historisé de cette opération en utilisant la commande 'yunohost log display {name} --share'",
|
||||
"log_does_exists": "Il n’existe pas de journal historisé de l’opération ayant pour nom '{log}', utiliser 'yunohost log list pour voir tous les fichiers de journaux historisés disponibles'",
|
||||
"log_operation_unit_unclosed_properly": "L’opération ne s’est pas terminée correctement",
|
||||
|
@ -454,7 +454,7 @@
|
|||
"log_user_create": "Ajouter l’utilisateur '{}'",
|
||||
"log_user_delete": "Supprimer l’utilisateur '{}'",
|
||||
"log_user_update": "Mettre à jour les informations de l’utilisateur '{}'",
|
||||
"log_tools_maindomain": "Faire de '{}' le domaine principal",
|
||||
"log_domain_main_domain": "Faire de '{}' le domaine principal",
|
||||
"log_tools_migrations_migrate_forward": "Migrer vers",
|
||||
"log_tools_migrations_migrate_backward": "Revenir en arrière",
|
||||
"log_tools_postinstall": "Faire la post-installation de votre serveur YunoHost",
|
||||
|
@ -462,18 +462,18 @@
|
|||
"log_tools_shutdown": "Éteindre votre serveur",
|
||||
"log_tools_reboot": "Redémarrer votre serveur",
|
||||
"mail_unavailable": "Cette adresse de courriel est réservée et doit être automatiquement attribuée au tout premier utilisateur",
|
||||
"migration_description_0004_php5_to_php7_pools": "Reconfiguration des groupes PHP pour utiliser PHP 7 au lieu de PHP 5",
|
||||
"migration_description_0004_php5_to_php7_pools": "Reconfigurez les groupes PHP pour utiliser PHP 7 au lieu de PHP 5",
|
||||
"migration_description_0005_postgresql_9p4_to_9p6": "Migration des bases de données de PostgreSQL 9.4 vers PostgreSQL 9.6",
|
||||
"migration_0005_postgresql_94_not_installed": "PostgreSQL n’a pas été installé sur votre système. Rien à faire !",
|
||||
"migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système :( …",
|
||||
"migration_0005_not_enough_space": "Il n’y a pas assez d’espace libre de disponible sur {path} pour lancer maintenant la migration :(.",
|
||||
"migration_0005_postgresql_96_not_installed": "PostgreSQL 9.4 a été trouvé et installé, mais pas PostgreSQL 9.6 !? Quelque chose d’étrange a dû arriver à votre système… :(",
|
||||
"migration_0005_not_enough_space": "Laissez suffisamment d'espace disponible dans {chemin} pour exécuter la migration.",
|
||||
"recommend_to_add_first_user": "La post-installation est terminée mais YunoHost a besoin d’au moins un utilisateur pour fonctionner correctement. Vous devez en ajouter un en utilisant la commande 'yunohost user create $nomdutilisateur' ou bien via l’interface d’administration web.",
|
||||
"service_description_php7.0-fpm": "exécute des applications écrites en PHP avec Nginx",
|
||||
"service_description_php7.0-fpm": "Exécute des applications écrites en PHP avec NGINX",
|
||||
"users_available": "Liste des utilisateurs disponibles :",
|
||||
"good_practices_about_admin_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe d’administration. Le mot de passe doit comporter au moins 8 caractères – bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères (majuscules, minuscules, chiffres et caractères spéciaux).",
|
||||
"good_practices_about_user_password": "Vous êtes maintenant sur le point de définir un nouveau mot de passe utilisateur. Le mot de passe doit comporter au moins 8 caractères - bien qu’il soit recommandé d’utiliser un mot de passe plus long (c’est-à-dire une phrase secrète) et/ou d’utiliser différents types de caractères tels que : majuscules, minuscules, chiffres et caractères spéciaux.",
|
||||
"migration_description_0006_sync_admin_and_root_passwords": "Synchroniser les mots de passe admin et root",
|
||||
"migration_0006_disclaimer": "YunoHost s’attendra désormais à ce que les mots de passe admin et root soient synchronisés. En exécutant cette migration, votre mot de passe root sera remplacé par le mot de passe administrateur.",
|
||||
"migration_0006_disclaimer": "YunoHost s'attend maintenant à ce que les mots de passe administrateur et racine soient synchronisés. Cette migration remplace votre mot de passe root par le mot de passe administrateur.",
|
||||
"migration_0006_done": "Votre mot de passe root a été remplacé par celui de votre adminitrateur.",
|
||||
"password_listed": "Ce mot de passe est l'un des mots de passe les plus utilisés dans le monde. Veuillez choisir quelque chose d'un peu plus singulier.",
|
||||
"password_too_simple_1": "Le mot de passe doit comporter au moins 8 caractères",
|
||||
|
@ -481,11 +481,11 @@
|
|||
"password_too_simple_3": "Le mot de passe doit comporter au moins 8 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux",
|
||||
"password_too_simple_4": "Le mot de passe doit comporter au moins 12 caractères et contenir des chiffres, des majuscules, des minuscules et des caractères spéciaux",
|
||||
"root_password_desynchronized": "Le mot de passe administrateur a été changé, mais YunoHost n’a pas pu le propager au mot de passe root !",
|
||||
"aborting": "Opération annulée.",
|
||||
"app_not_upgraded": "Les applications suivantes n'ont pas été mises à jour : {apps}",
|
||||
"aborting": "Annulation.",
|
||||
"app_not_upgraded": "L’application {failed_app} n’a pas été mise à jour et par conséquence les applications suivantes n’ont pas été mises à jour : {apps}",
|
||||
"app_start_install": "Installation de l'application {app} …",
|
||||
"app_start_remove": "Suppression de l'application {app} …",
|
||||
"app_start_backup": "Collecte des fichiers devant être sauvegardés pour {app} …",
|
||||
"app_start_backup": "Collecte des fichiers devant être sauvegardés pour l'application {app} …",
|
||||
"app_start_restore": "Restauration de l'application {app} …",
|
||||
"app_upgrade_several_apps": "Les applications suivantes seront mises à jour : {apps}",
|
||||
"ask_new_domain": "Nouveau domaine",
|
||||
|
@ -493,8 +493,8 @@
|
|||
"backup_actually_backuping": "Création d'une archive de sauvegarde à partir des fichiers collectés …",
|
||||
"backup_mount_archive_for_restore": "Préparation de l'archive pour restauration …",
|
||||
"confirm_app_install_warning": "Avertissement : cette application peut fonctionner mais n'est pas bien intégrée dans YunoHost. Certaines fonctionnalités telles que l'authentification unique et la sauvegarde/restauration peuvent ne pas être disponibles. L'installer quand même ? [{answers:s}] ",
|
||||
"confirm_app_install_danger": "AVERTISSEMENT ! Cette application est encore expérimentale (explicitement, elle ne fonctionne pas) et risque de casser votre système ! Vous ne devriez probablement PAS l'installer sans savoir ce que vous faites. Êtes-vous prêt à prendre ce risque ? [{answers:s}] ",
|
||||
"confirm_app_install_thirdparty": "AVERTISSEMENT ! L'installation d'applications tierces peut compromettre l'intégrité et la sécurité de votre système. Vous ne devriez probablement PAS l'installer si vous ne savez pas ce que vous faites. Êtes-vous prêt à prendre ce risque ? [{answers:s}] ",
|
||||
"confirm_app_install_danger": "DANGER! Cette application est connue pour être encore expérimentale (si elle ne fonctionne pas explicitement)! Vous ne devriez probablement PAS l'installer à moins de savoir ce que vous faites. AUCUN SUPPORT ne sera fourni si cette application ne fonctionne pas ou casse votre système ... Si vous êtes prêt à prendre ce risque de toute façon, tapez '{answers: s}'",
|
||||
"confirm_app_install_thirdparty": "DANGER! Cette application ne fait pas partie du catalogue d'applications de Yunohost. L'installation d'applications tierces peut compromettre l'intégrité et la sécurité de votre système. Vous ne devriez probablement PAS l'installer à moins de savoir ce que vous faites. AUCUN SUPPORT ne sera fourni si cette application ne fonctionne pas ou casse votre système ... Si vous êtes prêt à prendre ce risque de toute façon, tapez '{answers:s}'",
|
||||
"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 dpkg --configure -a'.",
|
||||
"dyndns_could_not_check_available": "Impossible de vérifier si {domain:s} est disponible chez {provider:s}.",
|
||||
"file_does_not_exist": "Le fichier dont le chemin est {path:s} n'existe pas.",
|
||||
|
@ -504,14 +504,14 @@
|
|||
"hook_json_return_error": "Échec de la lecture au retour du script {path:s}. Erreur : {msg:s}. Contenu brut : {raw_content}",
|
||||
"migration_description_0007_ssh_conf_managed_by_yunohost_step1": "La configuration SSH sera gérée par YunoHost (étape 1, automatique)",
|
||||
"migration_description_0008_ssh_conf_managed_by_yunohost_step2": "La configuration SSH sera gérée par YunoHost (étape 2, manuelle)",
|
||||
"migration_0007_cancelled": "YunoHost n'a pas réussi à améliorer la façon dont est gérée votre configuration SSH.",
|
||||
"migration_0007_cancelled": "Impossible d'améliorer la gestion de votre configuration SSH.",
|
||||
"migration_0007_cannot_restart": "SSH ne peut pas être redémarré après avoir essayé d'annuler la migration numéro 6.",
|
||||
"migration_0008_general_disclaimer": "Pour améliorer la sécurité de votre serveur, il est recommandé de laisser YunoHost gérer la configuration SSH. Votre configuration SSH actuelle diffère de la configuration recommandée. Si vous laissez YunoHost la reconfigurer, la façon dont vous vous connectez à votre serveur via SSH changera comme suit :",
|
||||
"migration_0008_port": " - vous devrez vous connecter en utilisant le port 22 au lieu de votre actuel port SSH personnalisé. N'hésitez pas à le reconfigurer ;",
|
||||
"migration_0008_root": " - vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l'utilisateur admin ;",
|
||||
"migration_0008_dsa": " - la clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d'invalider un avertissement effrayant de votre client SSH afin de revérifier l'empreinte de votre serveur ;",
|
||||
"migration_0008_warning": "Si vous comprenez ces avertissements et que vous acceptez de laisser YunoHost remplacer votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également passer la migration, bien que cela ne soit pas recommandé.",
|
||||
"migration_0008_no_warning": "Aucun risque majeur n'a été identifié concernant l'écrasement de votre configuration SSH - mais nous ne pouvons pas en être absolument sûrs ;) ! Si vous acceptez de laisser YunoHost remplacer votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également passer la migration, bien que cela ne soit pas recommandé.",
|
||||
"migration_0008_port": "- Vous devrez vous connecter en utilisant le port 22 au lieu de votre actuel port SSH personnalisé. N'hésitez pas à le reconfigurer ;",
|
||||
"migration_0008_root": "- Vous ne pourrez pas vous connecter en tant que root via SSH. Au lieu de cela, vous devrez utiliser l'utilisateur admin ;",
|
||||
"migration_0008_dsa": "- La clé DSA sera désactivée. Par conséquent, il se peut que vous ayez besoin d'invalider un avertissement effrayant de votre client SSH afin de revérifier l'empreinte de votre serveur ;",
|
||||
"migration_0008_warning": "Si vous comprenez ces avertissements et souhaitez que YunoHost écrase votre configuration actuelle, exécutez la migration. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.",
|
||||
"migration_0008_no_warning": "Remplacer votre configuration SSH devrait être sûr, bien que cela ne puisse être promis! Exécutez la migration pour la remplacer. Sinon, vous pouvez également ignorer la migration, bien que cela ne soit pas recommandé.",
|
||||
"migrations_success": "Migration {number} {name} réussie !",
|
||||
"pattern_password_app": "Désolé, les mots de passe ne doivent 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.",
|
||||
|
@ -523,8 +523,8 @@
|
|||
"service_reload_or_restart_failed": "Impossible de recharger ou de redémarrer le service '{service:s}'\n\nJournaux historisés récents de ce service : {logs:s}",
|
||||
"service_reloaded_or_restarted": "Le service '{service:s}' a été rechargé ou redémarré",
|
||||
"this_action_broke_dpkg": "Cette action a laissé des paquets non configurés par dpkg/apt (les gestionnaires de paquets système). Vous pouvez essayer de résoudre ce problème en vous connectant via SSH et en exécutant `sudo dpkg --configure -a`.",
|
||||
"app_action_cannot_be_ran_because_required_services_down": "Cette application requiert certains services qui sont actuellement arrêtés. Avant de continuer, vous devriez essayer de redémarrer les services suivants (et éventuellement rechercher pourquoi ils sont arrêtés) : {services}",
|
||||
"admin_password_too_long": "Choisissez un mot de passe plus court que 127 caractères",
|
||||
"app_action_cannot_be_ran_because_required_services_down": "Ces services requis doivent être en cours d'exécution pour exécuter cette action: {services}. Essayez de les redémarrer pour continuer (et éventuellement rechercher pourquoi ils sont en panne).",
|
||||
"admin_password_too_long": "Veuillez choisir un mot de passe de moins de 127 caractères",
|
||||
"log_regen_conf": "Régénérer les configurations du système '{}'",
|
||||
"migration_0009_not_needed": "Cette migration semble avoir déjà été jouée ? On l'ignore.",
|
||||
"regenconf_file_backed_up": "Le fichier de configuration '{conf}' a été sauvegardé sous '{backup}'",
|
||||
|
@ -549,17 +549,121 @@
|
|||
"regenconf_failed": "Impossible de régénérer la configuration pour la ou les catégorie(s) : '{categories}'",
|
||||
"regenconf_pending_applying": "Applique la configuration en attente pour la catégorie '{category}' …",
|
||||
"service_regen_conf_is_deprecated": "'yunohost service regen-conf' est obsolète ! Veuillez plutôt utiliser 'yunohost tools regen-conf' à la place.",
|
||||
"tools_upgrade_at_least_one": "Veuillez spécifier --apps OU --system",
|
||||
"tools_upgrade_at_least_one": "Veuillez spécifier '--apps' OU '--system'",
|
||||
"tools_upgrade_cant_both": "Impossible de mettre à niveau le système et les applications en même temps",
|
||||
"tools_upgrade_cant_hold_critical_packages": "Impossibilité de maintenir les paquets critiques...",
|
||||
"tools_upgrade_regular_packages": "Mise à jour des paquets du système (non liés a YunoHost) ...",
|
||||
"tools_upgrade_cant_hold_critical_packages": "Impossibilité de maintenir les paquets critiques…",
|
||||
"tools_upgrade_regular_packages": "Mise à jour des paquets du système (non liés a YunoHost) …",
|
||||
"tools_upgrade_regular_packages_failed": "Impossible de mettre à jour les paquets suivants : {packages_list}",
|
||||
"tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) ...",
|
||||
"tools_upgrade_special_packages": "Mise à jour des paquets 'spécifiques' (liés a YunoHost) …",
|
||||
"tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie!\nPressez [Entrée] pour revenir à la ligne de commande",
|
||||
"updating_app_lists": "Récupération des mises à jour des applications disponibles…",
|
||||
"dpkg_lock_not_available": "Cette commande ne peut être lancée maintenant car il semblerai qu'un autre programme utilise déjà le verrou dpkg du gestionnaire de paquets du système",
|
||||
"tools_upgrade_cant_unhold_critical_packages": "Impossible de dé-marquer les paquets critiques ...",
|
||||
"tools_upgrade_special_packages_explanation": "Cette opération prendra fin mais la mise à jour spécifique continuera en arrière-plan. Veuillez ne pas lancer d'autre action sur votre serveur dans les 10 prochaines minutes (en fonction de la vitesse de votre matériel). Une fois que c'est fait, vous devrez peut-être vous reconnecter sur le panel d'administration web. Le journal de la mise à jour sera disponible dans Outils > Log (dans le panel d'administration web) ou dans la liste des journaux YunoHost (en ligne de commande).",
|
||||
"dpkg_lock_not_available": "Cette commande ne peut être exécutée actuellement car un autre programme semble utiliser le verrou de dpkg (gestionnaire de paquets)",
|
||||
"tools_upgrade_cant_unhold_critical_packages": "Impossible de conserver les paquets critiques…",
|
||||
"tools_upgrade_special_packages_explanation": "Cette action se terminera, mais la mise à niveau spéciale réelle continuera en arrière-plan. Veuillez ne pas lancer d’autres actions sur votre serveur au cours des 10 prochaines minutes (en fonction de la vitesse du matériel). Une fois cela fait, vous devrez peut-être vous reconnecter à la page Webadmin. Le journal de mise à niveau sera disponible dans Outils → Journal (sur la page Webadmin) ou dans la \"liste des journaux yunohost\" (à partir de la ligne de commande).",
|
||||
"update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}",
|
||||
"update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}"
|
||||
"update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}",
|
||||
"apps_permission_not_found": "Aucune permission trouvée pour les applications installées",
|
||||
"apps_permission_restoration_failed": "L'autorisation '{permission:s}' pour la restauration de l'application {app:s} a échoué",
|
||||
"backup_permission": "Permission de sauvegarde pour l'application {app:s}",
|
||||
"edit_group_not_allowed": "Vous n'êtes pas autorisé à modifier le groupe {group:s}",
|
||||
"error_when_removing_sftpuser_group": "Vous ne pouvez pas supprimer le groupe sftpusers",
|
||||
"group_created": "Le groupe '{group}' a été créé",
|
||||
"group_deleted": "Suppression du groupe '{group}'",
|
||||
"group_deletion_not_allowed": "Le groupe {group:s} ne peut pas être supprimé manuellement.",
|
||||
"group_info_failed": "L'information sur le groupe a échoué",
|
||||
"group_unknown": "Le groupe {group:s} est inconnu",
|
||||
"group_updated": "Le groupe '{group}' a été mis à jour",
|
||||
"group_update_failed": "La mise à jour du groupe '{group}' a échoué : {error}",
|
||||
"group_already_allowed": "Le groupe '{group:s}' a déjà la permission '{permission:s}' activée pour l'application '{app:s}'",
|
||||
"group_already_disallowed": "Le groupe '{group:s}' a déjà la permission '{permission:s}' désactivée pour l'application '{app:s}'",
|
||||
"group_name_already_exist": "Le groupe {name:s} existe déjà",
|
||||
"group_creation_failed": "Échec de la création du groupe '{group}': {error}",
|
||||
"group_deletion_failed": "Échec de la suppression du groupe '{group}': {error}",
|
||||
"edit_permission_with_group_all_users_not_allowed": "Vous n'êtes pas autorisé à modifier les permissions pour le groupe 'all_users', utilisez 'yunohost user permission clear APP' ou 'yunohost user permission add APP -u USER' à la place.",
|
||||
"log_permission_add": "Ajouter l'autorisation '{}' pour l'application '{}'",
|
||||
"log_permission_remove": "Supprimer l'autorisation '{}'",
|
||||
"log_permission_update": "Mise à jour de l'autorisation '{}' pour l'application '{}'",
|
||||
"log_user_group_add": "Ajouter '{}' au groupe",
|
||||
"log_user_group_delete": "Supprimer le groupe '{}'",
|
||||
"log_user_group_update": "Mettre à jour '{}' pour le groupe",
|
||||
"log_user_permission_add": "Mettre à jour l'autorisation pour '{}'",
|
||||
"log_user_permission_remove": "Mettre à jour l'autorisation pour '{}'",
|
||||
"mailbox_disabled": "La boîte aux lettres est désactivée pour l'utilisateur {user:s}",
|
||||
"app_action_broke_system": "Cette action semble avoir cassé des services important : {services}",
|
||||
"apps_already_up_to_date": "Toutes les applications sont déjà à jour",
|
||||
"app_upgrade_stopped": "La mise à niveau de toutes les applications s'est arrêtée pour éviter tout dommage, car une application n'a pas pu être mise à niveau.",
|
||||
"migration_0011_create_group": "Créer un groupe pour chaque utilisateur…",
|
||||
"migration_0011_done": "Migration terminée. Vous êtes maintenant en mesure de gérer des groupes d'utilisateurs.",
|
||||
"migrations_must_provide_explicit_targets": "Vous devez fournir des cibles explicites lorsque vous utilisez '--skip' ou '--force-rerun'",
|
||||
"migrations_no_such_migration": "Il n'y a pas de migration appelée '{id}'",
|
||||
"migrations_pending_cant_rerun": "Ces migrations étant toujours en attente, vous ne pouvez pas les exécuter à nouveau: {ids}",
|
||||
"migration_description_0012_postgresql_password_to_md5_authentication": "Forcer l'authentification PostgreSQL à utiliser MD5 pour les connexions locales",
|
||||
"migrations_exclusive_options": "'auto', '--skip' et '--force-rerun' sont des options mutuellement exclusives.",
|
||||
"migrations_not_pending_cant_skip": "Ces migrations ne sont pas en attente et ne peuvent donc pas être ignorées: {ids}",
|
||||
"migration_0011_can_not_backup_before_migration": "La sauvegarde du système n'a pas pu être terminée avant l'échec de la migration. Erreur: {erreur:s}",
|
||||
"migration_0011_migrate_permission": "Migration des autorisations des paramètres des applications vers LDAP…",
|
||||
"migration_0011_migration_failed_trying_to_rollback": "Impossible de migrer… en essayant de restaurer le système.",
|
||||
"migration_0011_rollback_success": "Système restauré.",
|
||||
"migration_0011_update_LDAP_database": "Mise à jour de la base de données LDAP…",
|
||||
"system_groupname_exists": "Le nom de groupe existe déjà dans le groupe du systèmes",
|
||||
"tools_update_failed_to_app_fetchlist": "Impossible de mettre à jour les listes d'applications de YunoHost car: {error}",
|
||||
"user_already_in_group": "L'utilisateur '{user:}' est déjà dans le groupe '{group: s}'",
|
||||
"user_not_in_group": "L'utilisateur '{user: s}' ne fait pas partie du groupe {group: s}",
|
||||
"migration_0011_backup_before_migration": "Création d'une sauvegarde des paramètres de la base de données LDAP et des applications avant la migration.",
|
||||
"permission_not_found": "Autorisation '{permission:s}' introuvable",
|
||||
"permission_name_not_valid": "Choisissez un nom d'autorisation autorisé pour '{permission: s}'",
|
||||
"permission_update_failed": "Impossible de mettre à jour la permission '{permission}': {error}",
|
||||
"permission_generated": "Base de données des autorisations mise à jour",
|
||||
"permission_updated": "Permission '{permission:s}' mise à jour",
|
||||
"permission_update_nothing_to_do": "Aucune autorisation pour mettre à jour",
|
||||
"remove_main_permission_not_allowed": "Supprimer l'autorisation principale n'est pas autorisé",
|
||||
"dyndns_provider_unreachable": "Impossible d’atteindre le fournisseur Dyndns {provider}: votre YunoHost n’est pas correctement connecté à Internet ou le serveur Dynette est en panne.",
|
||||
"migration_0011_update_LDAP_schema": "Mise à jour du schéma LDAP…",
|
||||
"migrations_already_ran": "Ces migrations sont déjà effectuées: {ids}",
|
||||
"migrations_dependencies_not_satisfied": "Exécutez ces migrations: '{dependencies_id}', avant migration {id}.",
|
||||
"migrations_failed_to_load_migration": "Impossible de charger la migration {id}: {error}",
|
||||
"migrations_running_forward": "Exécution de la migration {id}…",
|
||||
"migrations_success_forward": "Migration {id} terminée",
|
||||
"need_define_permission_before": "Redéfinissez l'autorisation à l'aide de 'yunohost user permission add -u USER' avant de supprimer un groupe autorisé",
|
||||
"operation_interrupted": "L'opération a-t-elle été interrompue manuellement ?",
|
||||
"permission_already_clear": "L'autorisation '{permission: s}' est déjà vide pour l'application {app: s}",
|
||||
"permission_already_exist": "L'autorisation '{permission}' existe déjà",
|
||||
"permission_created": "Permission '{permission:s}' créée",
|
||||
"permission_creation_failed": "Impossible de créer l'autorisation '{permission}': {erreur}",
|
||||
"permission_deleted": "Permission '{permission:s}' supprimée",
|
||||
"permission_deletion_failed": "Impossible de supprimer la permission '{permission}': {error}",
|
||||
"remove_user_of_group_not_allowed": "Vous n'êtes pas autorisé à supprimer l'utilisateur '{utilisateur: s}' dans le groupe '{groupe: s}'",
|
||||
"migration_description_0011_setup_group_permission": "Configurer le groupe d'utilisateurs et configurer les autorisations pour les applications et les services",
|
||||
"migration_0011_LDAP_config_dirty": "Il semble que vous ayez personnalisé votre configuration LDAP. Pour cette migration, la configuration LDAP doit être mise à jour.\nVous devez enregistrer votre configuration actuelle, réintialiser la configuration d'origine en exécutant 'yunohost tools regen-conf -f', puis réessayer la migration",
|
||||
"migration_0011_LDAP_update_failed": "Impossible de mettre à jour LDAP. Erreur: {error: s}",
|
||||
"group_already_exist": "Le groupe {group} existe déjà",
|
||||
"group_already_exist_on_system": "Le groupe {group} existe déjà dans les groupes système",
|
||||
"group_cannot_be_edited": "Le groupe {group} ne peut pas être édité manuellement.",
|
||||
"group_cannot_be_deleted": "Le groupe {group} ne peut pas être supprimé manuellement.",
|
||||
"group_user_already_in_group": "L'utilisateur {user} est déjà dans le groupe {group}",
|
||||
"group_user_not_in_group": "L'utilisateur {user} n'est pas dans le groupe {group}",
|
||||
"log_permission_create": "Créer permission '{}'",
|
||||
"log_permission_delete": "supprimer permission '{}'",
|
||||
"log_permission_urls": "Mettre à jour les URL liées à la permission '{}'",
|
||||
"log_user_group_create": "Créer '{}' groupe",
|
||||
"log_user_permission_update": "Mise à jour des accès pour la permission '{}'",
|
||||
"log_user_permission_reset": "Réinitialiser la permission '{}'",
|
||||
"migration_0011_failed_to_remove_stale_object": "Impossible de supprimer un objet périmé {dn}: {error}",
|
||||
"permission_already_allowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' activée",
|
||||
"permission_already_disallowed": "Le groupe '{group}' a déjà l'autorisation '{permission}' désactivé '",
|
||||
"permission_cannot_remove_main": "Supprimer une autorisation principale n'est pas autorisé",
|
||||
"user_already_exists": "L'utilisateur '{user}' existe déjà",
|
||||
"app_full_domain_unavailable": "Désolé, cette application doit être installée sur un domaine qui lui est propre, mais d'autres applications sont déjà installées sur le domaine '{domain}'. Vous pouvez utiliser un sous-domaine dédié à cette application à la place.",
|
||||
"group_cannot_edit_all_users": "Le groupe 'all_users' ne peut pas être édité manuellement. C'est un groupe spécial destiné à contenir tous les utilisateurs enregistrés dans YunoHost",
|
||||
"group_cannot_edit_visitors": "Le groupe 'visiteurs' ne peut pas être édité manuellement. C'est un groupe spécial représentant les visiteurs anonymes",
|
||||
"group_cannot_edit_primary_group": "Le groupe '{group}' ne peut pas être édité manuellement. C'est le groupe principal destiné à ne contenir qu'un utilisateur spécifique.",
|
||||
"log_permission_url": "Mise à jour de l'URL associée à l'autorisation '{}'",
|
||||
"migration_0011_slapd_config_will_be_overwritten": "Il semble que vous ayez modifié manuellement la configuration de slapd. Pour cette migration critique, YunoHost doit forcer la mise à jour de la configuration de slapd. Les fichiers originaux seront sauvegardés dans {conf_backup_folder}.",
|
||||
"permission_already_up_to_date": "L'autorisation n'a pas été mise à jour car les demandes d'ajout/suppression correspondent déjà à l'état actuel.",
|
||||
"permission_currently_allowed_for_visitors": "Cette autorisation est actuellement accordée aux visiteurs en plus d'autres groupes. Vous voudrez probablement supprimer l'autorisation \"visiteurs\" ou supprimer les autres groupes auxquels il est actuellement attribué.",
|
||||
"permission_currently_allowed_for_all_users": "Cette autorisation est actuellement accordée à tous les utilisateurs en plus des autres groupes. Vous voudrez probablement soit supprimer l'autorisation 'all_users', soit supprimer les autres groupes auxquels il est actuellement autorisé.",
|
||||
"app_install_failed": "Impossible d'installer {app}: {error}",
|
||||
"app_install_script_failed": "Une erreur est survenue dans le script d'installation de l'application",
|
||||
"permission_require_account": "Permission {permission} n'a de sens que pour les utilisateurs ayant un compte et ne peut donc pas être activé pour les visiteurs.",
|
||||
"app_remove_after_failed_install": "Supprimer l'application après l'échec de l'installation…"
|
||||
}
|
||||
|
|
|
@ -136,8 +136,8 @@
|
|||
"mail_domain_unknown": "Dominio d'indirizzo mail '{domain:s}' sconosciuto",
|
||||
"mail_forward_remove_failed": "Impossibile rimuovere la mail inoltrata '{mail:s}'",
|
||||
"mailbox_used_space_dovecot_down": "Il servizio di posta elettronica Dovecot deve essere attivato se vuoi riportare lo spazio usato dalla posta elettronica",
|
||||
"maindomain_change_failed": "Impossibile cambiare il dominio principale",
|
||||
"maindomain_changed": "Il dominio principale è stato cambiato",
|
||||
"main_domain_change_failed": "Impossibile cambiare il dominio principale",
|
||||
"main_domain_changed": "Il dominio principale è stato cambiato",
|
||||
"monitor_disabled": "Il monitoraggio del sistema è stato disattivato",
|
||||
"monitor_enabled": "Il monitoraggio del sistema è stato attivato",
|
||||
"monitor_glances_con_failed": "Impossibile collegarsi al server Glances",
|
||||
|
@ -402,7 +402,7 @@
|
|||
"log_user_create": "Aggiungi l'utente '{}'",
|
||||
"log_user_delete": "Elimina l'utente '{}'",
|
||||
"log_user_update": "Aggiornate le informazioni dell'utente '{}'",
|
||||
"log_tools_maindomain": "Rendi '{}' dominio principale",
|
||||
"log_domain_main_domain": "Rendi '{}' dominio principale",
|
||||
"log_tools_migrations_migrate_forward": "Migra avanti",
|
||||
"log_tools_migrations_migrate_backward": "Migra indietro",
|
||||
"log_tools_postinstall": "Postinstallazione del tuo server YunoHost",
|
||||
|
|
|
@ -1 +1,169 @@
|
|||
{}
|
||||
{
|
||||
"aborting": "Avbryter…",
|
||||
"admin_password": "Administrasjonspassord",
|
||||
"admin_password_change_failed": "Kan ikke endre passord",
|
||||
"admin_password_changed": "Administrasjonspassord endret",
|
||||
"admin_password_too_long": "Velg et passord kortere enn 127 tegn",
|
||||
"app_already_installed": "{app:s} er allerede installert",
|
||||
"app_already_up_to_date": "{app:s} er allerede oppdatert",
|
||||
"app_argument_invalid": "Velg en gydlig verdi for argumentet '{name:s}': {error:s}",
|
||||
"app_argument_required": "Argumentet '{name:s}' er påkrevd",
|
||||
"diagnosis_no_apps": "Inget program installert",
|
||||
"app_id_invalid": "Ugyldig program-ID",
|
||||
"dyndns_cron_remove_failed": "Kunne ikke fjerne cron-jobb for DynDNS: {error}",
|
||||
"dyndns_key_not_found": "Fant ikke DNS-nøkkel for domenet",
|
||||
"app_not_correctly_installed": "{app:s} ser ikke ut til å ha blitt installert på riktig måte",
|
||||
"dyndns_provider_unreachable": "Kunne ikke nå DynDNS-tilbyder {provider}: Enten har du ikke satt opp din YunoHost rett, dynette-tjeneren er nede, eller du mangler nett.",
|
||||
"app_not_properly_removed": "{app:s} har ikke blitt fjernet på riktig måte",
|
||||
"app_package_need_update": "Programmet {app}-pakken må oppdateres for å holde følge med YunoHost sine endringer",
|
||||
"app_removed": "{app:s} fjernet",
|
||||
"app_requirements_checking": "Sjekker påkrevde pakker for {app:s}…",
|
||||
"app_requirements_failed": "Noen krav er ikke oppfylt for {app:s}: {error}",
|
||||
"app_start_install": "Installerer programmet '{app}'…",
|
||||
"action_invalid": "Ugyldig handling '{action:s}'",
|
||||
"app_start_restore": "Gjenoppretter programmet '{app}'…",
|
||||
"backup_created": "Sikkerhetskopi opprettet",
|
||||
"backup_archive_name_exists": "En sikkerhetskopi med dette navnet finnes allerede.",
|
||||
"backup_archive_name_unknown": "Ukjent lokalt sikkerhetskopiarkiv ved navn '{name:s}'",
|
||||
"already_up_to_date": "Ingenting å gjøre. Alt er oppdatert.",
|
||||
"backup_method_copy_finished": "Sikkerhetskopi fullført",
|
||||
"backup_method_tar_finished": "TAR-sikkerhetskopiarkiv opprettet",
|
||||
"app_action_cannot_be_ran_because_required_services_down": "Dette programmet krever noen tjenester som ikke kjører. Før du fortsetter, du bør prøve å starte følgende tjenester på ny (og antagelig undersøke hvorfor de er nede): {services}",
|
||||
"app_already_installed_cant_change_url": "Dette programmet er allerede installert. Nettadressen kan ikke endres kun med denne funksjonen. Ta en titt på `app changeurl` hvis den er tilgjengelig.",
|
||||
"diagnosis_monitor_disk_error": "Kunne ikke holde oppsyn med disker: {error}",
|
||||
"diagnosis_monitor_system_error": "Kunne ikke holde oppsyn med systemet: {error}",
|
||||
"domain_exists": "Domenet finnes allerede",
|
||||
"app_change_url_failed_nginx_reload": "Kunne ikke gjeninnlaste NGINX. Her har du utdataen for 'nginx -t'\n{nginx_errors:s}",
|
||||
"domains_available": "Tilgjengelige domener:",
|
||||
"done": "Ferdig",
|
||||
"downloading": "Laster ned…",
|
||||
"dyndns_could_not_check_provide": "Kunne ikke sjekke om {provider:s} kan tilby {domain:s}.",
|
||||
"dyndns_could_not_check_available": "Kunne ikke sjekke om {domain:s} er tilgjengelig på {provider:s}.",
|
||||
"log_app_removeaccess": "Fjern tilgang til '{}'",
|
||||
"license_undefined": "udefinert",
|
||||
"mail_domain_unknown": "Ukjent e-postadresse for domenet '{domain:s}'",
|
||||
"migrate_tsig_wait_2": "2 min…",
|
||||
"log_remove_on_failed_restore": "Fjern '{}' etter mislykket gjenoppretting fra sikkerhetskopiarkiv",
|
||||
"log_letsencrypt_cert_install": "Installer et Let's Encrypt-sertifikat på '{}'-domenet",
|
||||
"log_permission_update": "Oppdater tilgang '{}' for programmet '{}'",
|
||||
"log_letsencrypt_cert_renew": "Forny '{}'-Let's Encrypt-sertifikat",
|
||||
"log_user_update": "Oppdater brukerinfo for '{}'",
|
||||
"mail_alias_remove_failed": "Kunne ikke fjerne e-postaliaset '{mail:s}'",
|
||||
"app_action_broke_system": "Denne handlingen ser ut til å ha knekt disse viktige tjenestene: {services}",
|
||||
"app_argument_choice_invalid": "Bruk én av disse valgene '{choices:s}' for argumentet '{name:s}'",
|
||||
"app_extraction_failed": "Kunne ikke pakke ut installasjonsfilene",
|
||||
"app_incompatible": "Programmet {app} er ikke kompatibelt med din YunoHost-versjon",
|
||||
"app_install_files_invalid": "Disse filene kan ikke installeres",
|
||||
"app_location_already_used": "Programmet '{app}' er allerede installert i ({path})",
|
||||
"ask_path": "Sti",
|
||||
"backup_abstract_method": "Denne sikkerhetskopimetoden er ikke implementert enda",
|
||||
"backup_actually_backuping": "Oppretter sikkerhetskopiarkiv fra innsamlede filer…",
|
||||
"backup_app_failed": "Kunne ikke sikkerhetskopiere programmet '{app:s}'",
|
||||
"backup_applying_method_tar": "Lager TAR-sikkerhetskopiarkiv…",
|
||||
"backup_archive_app_not_found": "Fant ikke programmet '{app:s}' i sikkerhetskopiarkivet",
|
||||
"backup_archive_open_failed": "Kunne ikke åpne sikkerhetskopiarkivet",
|
||||
"app_start_remove": "Fjerner programmet '{app}'…",
|
||||
"app_start_backup": "Samler inn filer for sikkerhetskopiering for {app}…",
|
||||
"backup_applying_method_copy": "Kopier alle filer til sikkerhetskopi…",
|
||||
"backup_borg_not_implemented": "Borg-sikkerhetskopimetoden er ikke implementert enda",
|
||||
"backup_creation_failed": "Kunne ikke opprette sikkerhetskopiarkiv",
|
||||
"backup_couldnt_bind": "Kunne ikke binde {src:s} til {dest:s}.",
|
||||
"backup_csv_addition_failed": "Kunne ikke legge til filer for sikkerhetskopi inn i CSV-filen",
|
||||
"backup_deleted": "Sikkerhetskopi slettet",
|
||||
"backup_no_uncompress_archive_dir": "Det finnes ingen slik utpakket arkivmappe",
|
||||
"backup_delete_error": "Kunne ikke slette '{path:s}'",
|
||||
"certmanager_domain_unknown": "Ukjent domene '{domain:s}'",
|
||||
"certmanager_cert_signing_failed": "Kunne ikke signere det nye sertifikatet",
|
||||
"diagnosis_debian_version_error": "Kunne ikke hente Debian-versjon: {error}",
|
||||
"diagnosis_kernel_version_error": "Kunne ikke hente kjerneversjon: {error}",
|
||||
"error_when_removing_sftpuser_group": "Kunne ikke fjerne sftpusers-gruppen",
|
||||
"executing_command": "Kjører kommendoen '{command:s}'…",
|
||||
"executing_script": "Kjører skriptet '{script:s}'…",
|
||||
"extracting": "Pakker ut…",
|
||||
"edit_group_not_allowed": "Du tillates ikke å redigere gruppen {group:s}",
|
||||
"log_domain_add": "Legg til '{}'-domenet i systemoppsett",
|
||||
"log_domain_remove": "Fjern '{}'-domenet fra systemoppsett",
|
||||
"log_dyndns_subscribe": "Abonner på YunoHost-underdomenet '{}'",
|
||||
"log_dyndns_update": "Oppdater IP-adressen tilknyttet ditt YunoHost-underdomene '{}'",
|
||||
"migrate_tsig_wait_3": "1 min…",
|
||||
"migrate_tsig_wait_4": "30 sekunder…",
|
||||
"apps_permission_restoration_failed": "Innvilg tilgangen '{permission:s}' for å gjenopprette {app:}",
|
||||
"apps_permission_not_found": "Fant ingen tilgang for de installerte programmene",
|
||||
"backup_invalid_archive": "Dette er ikke et sikkerhetskopiarkiv",
|
||||
"backup_nothings_done": "Ingenting å lagre",
|
||||
"backup_method_borg_finished": "Sikkerhetskopi inn i Borg fullført",
|
||||
"field_invalid": "Ugyldig felt '{:s}'",
|
||||
"firewall_reloaded": "Brannmur gjeninnlastet",
|
||||
"log_app_removelist": "Fjern en programliste",
|
||||
"log_app_change_url": "Endre nettadresse for '{}'-programmet",
|
||||
"log_app_install": "Installer '{}'-programmet",
|
||||
"log_app_remove": "Fjern '{}'-programmet",
|
||||
"log_app_upgrade": "Oppgrader '{}'-programmet",
|
||||
"log_app_makedefault": "Gjør '{}' til forvalgt program",
|
||||
"log_available_on_yunopaste": "Denne loggen er nå tilgjengelig via {url}",
|
||||
"log_tools_maindomain": "Gjør '{}' til hoveddomene",
|
||||
"log_tools_shutdown": "Slå av tjeneren din",
|
||||
"log_tools_reboot": "Utfør omstart av tjeneren din",
|
||||
"apps_already_up_to_date": "Alle programmer allerede oppdatert",
|
||||
"backup_mount_archive_for_restore": "Forbereder arkiv for gjenopprettelse…",
|
||||
"backup_copying_to_organize_the_archive": "Kopierer {size:s} MB for å organisere arkivet",
|
||||
"domain_cannot_remove_main": "Kan ikke fjerne hoveddomene. Sett et først",
|
||||
"domain_cert_gen_failed": "Kunne ikke opprette sertifikat",
|
||||
"domain_created": "Domene opprettet",
|
||||
"domain_creation_failed": "Kunne ikke opprette domene",
|
||||
"domain_dyndns_root_unknown": "Ukjent DynDNS-rotdomene",
|
||||
"domain_unknown": "Ukjent domene",
|
||||
"dyndns_cron_installed": "Opprettet cron-jobb for DynDNS",
|
||||
"dyndns_cron_removed": "Fjernet cron-jobb for DynDNS",
|
||||
"dyndns_ip_update_failed": "Kunne ikke oppdatere IP-adresse til DynDNS",
|
||||
"dyndns_ip_updated": "Oppdaterte din IP på DynDNS",
|
||||
"dyndns_key_generating": "Oppretter DNS-nøkkel… Dette kan ta en stund.",
|
||||
"dyndns_no_domain_registered": "Inget domene registrert med DynDNS",
|
||||
"dyndns_registered": "DynDNS-domene registrert",
|
||||
"global_settings_setting_security_password_admin_strength": "Admin-passordets styrke",
|
||||
"dyndns_registration_failed": "Kunne ikke registrere DynDNS-domene: {error:s}",
|
||||
"global_settings_setting_security_password_user_strength": "Brukerpassordets styrke",
|
||||
"log_app_fetchlist": "Legg til en programliste",
|
||||
"log_backup_restore_app": "Gjenopprett '{}' fra sikkerhetskopiarkiv",
|
||||
"log_remove_on_failed_install": "Fjern '{}' etter mislykket installasjon",
|
||||
"log_permission_add": "Legg til '{}'-tilgangen for programmet '{}'",
|
||||
"log_permission_remove": "Fjern tilgangen '{}'",
|
||||
"log_selfsigned_cert_install": "Installer selvsignert sertifikat på '{}'-domenet",
|
||||
"log_user_delete": "Slett '{}' bruker",
|
||||
"log_user_group_add": "Legg til '{}' gruppe",
|
||||
"log_user_group_delete": "Slett '{}' gruppe",
|
||||
"log_user_group_update": "Oppdater '{}' gruppe",
|
||||
"log_user_permission_add": "Oppdater '{}' tilgang",
|
||||
"log_user_permission_remove": "Oppdater '{}' tilgang",
|
||||
"ldap_init_failed_to_create_admin": "LDAP-igangsettelse kunne ikke opprette admin-bruker",
|
||||
"ldap_initialized": "LDAP-igangsatt",
|
||||
"maindomain_changed": "Hoveddomenet er nå endret",
|
||||
"migration_description_0003_migrate_to_stretch": "Oppgrader systemet til Debian Stretch og YunoHost 3.0",
|
||||
"app_unknown": "Ukjent program",
|
||||
"app_upgrade_app_name": "Oppgraderer {app}…",
|
||||
"app_upgrade_failed": "Kunne ikke oppgradere {app:s}",
|
||||
"app_upgrade_some_app_failed": "Noen programmer kunne ikke oppgraderes",
|
||||
"app_upgraded": "{app:s} oppgradert",
|
||||
"ask_email": "E-postadresse",
|
||||
"ask_firstname": "Fornavn",
|
||||
"ask_lastname": "Etternavn",
|
||||
"ask_list_to_remove": "Liste å fjerne",
|
||||
"ask_main_domain": "Hoveddomene",
|
||||
"ask_new_admin_password": "Nytt administrasjonspassord",
|
||||
"app_upgrade_several_apps": "Følgende programmer vil oppgraderes: {apps}",
|
||||
"appslist_removed": "{appslist:s}-programliste fjernet",
|
||||
"appslist_url_already_tracked": "Dette er allerede en registrert programliste med nettadressen {url:s}.",
|
||||
"ask_current_admin_password": "Nåværende administrasjonspassord",
|
||||
"appslist_unknown": "Programlisten {appslist:s} er ukjent.",
|
||||
"ask_new_domain": "Nytt domene",
|
||||
"ask_new_path": "Ny sti",
|
||||
"ask_password": "Passord",
|
||||
"domain_deleted": "Domene slettet",
|
||||
"domain_deletion_failed": "Kunne ikke slette domene",
|
||||
"domain_dyndns_already_subscribed": "Du har allerede abonnement på et DynDNS-domene",
|
||||
"log_category_404": "Loggkategorien '{category}' finnes ikke",
|
||||
"log_link_to_log": "Full logg for denne operasjonen: '<a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>'",
|
||||
"log_help_to_get_log": "For å vise loggen for operasjonen '{desc}', bruk kommandoen 'yunohost log display {name}'",
|
||||
"log_app_clearaccess": "Fjern all tilgang til '{}'",
|
||||
"log_user_create": "Legg til '{}' bruker"
|
||||
}
|
||||
|
|
123
locales/oc.json
123
locales/oc.json
|
@ -7,16 +7,16 @@
|
|||
"installation_complete": "Installacion acabada",
|
||||
"app_id_invalid": "Id d’aplicacion incorrècte",
|
||||
"app_install_files_invalid": "Fichièrs d’installacion incorrèctes",
|
||||
"app_no_upgrade": "Pas cap d’aplicacion de metre a jorn",
|
||||
"app_no_upgrade": "Pas cap d’aplicacion d’actualizar",
|
||||
"app_not_correctly_installed": "{app:s} sembla pas ben installat",
|
||||
"app_not_installed": "{app:s} es pas installat",
|
||||
"app_not_installed": "L’aplicacion {app:s} es pas installat. Vaquí la lista de las aplicacions installadas : {all_apps}",
|
||||
"app_not_properly_removed": "{app:s} es pas estat corrèctament suprimit",
|
||||
"app_removed": "{app:s} es estat suprimit",
|
||||
"app_unknown": "Aplicacion desconeguda",
|
||||
"app_upgrade_app_name": "Mesa a jorn de l’aplicacion {app}…",
|
||||
"app_upgrade_failed": "Impossible de metre a jorn {app:s}",
|
||||
"app_upgrade_some_app_failed": "D’aplicacions se pòdon pas metre a jorn",
|
||||
"app_upgraded": "{app:s} es estat mes a jorn",
|
||||
"app_upgrade_app_name": "Actualizacion de l’aplicacion {app}…",
|
||||
"app_upgrade_failed": "Impossible d’actualizar {app:s}",
|
||||
"app_upgrade_some_app_failed": "D’aplicacions se pòdon pas actualizar",
|
||||
"app_upgraded": "{app:s} es estada actualizada",
|
||||
"appslist_fetched": "Recuperacion de la lista d’aplicacions {appslist:s} corrèctament realizada",
|
||||
"appslist_migrating": "Migracion de la lista d’aplicacion{appslist:s}…",
|
||||
"appslist_name_already_tracked": "I a ja una lista d’aplicacion enregistrada amb lo nom {name:s}.",
|
||||
|
@ -51,7 +51,7 @@
|
|||
"app_incompatible": "L’aplicacion {app} es pas compatibla amb vòstra version de YunoHost",
|
||||
"app_location_already_used": "L’aplicacion « {app} » es ja installada a aqueste emplaçament ({path})",
|
||||
"app_manifest_invalid": "Manifest d’aplicacion incorrècte : {error}",
|
||||
"app_package_need_update": "Lo paquet de l’aplicacion {app} deu èsser mes a jorn per seguir los cambiaments de YunoHost",
|
||||
"app_package_need_update": "Lo paquet de l’aplicacion {app} deu èsser actualizat per poder seguir los cambiaments de YunoHost",
|
||||
"app_requirements_checking": "Verificacion dels paquets requesits per {app}…",
|
||||
"app_sources_fetch_failed": "Recuperacion dels fichièrs fonts impossibla, l’URL es corrècta ?",
|
||||
"app_unsupported_remote_type": "Lo tipe alonhat utilizat per l’aplicacion es pas suportat",
|
||||
|
@ -67,8 +67,8 @@
|
|||
"backup_creating_archive": "Creacion de l’archiu de salvagarda…",
|
||||
"backup_creation_failed": "Impossible de crear la salvagarda",
|
||||
"app_already_installed_cant_change_url": "Aquesta aplicacion es ja installada. Aquesta foncion pòt pas simplament cambiar l’URL. Agachatz « app changeurl » s’es disponible.",
|
||||
"app_change_no_change_url_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, poiretz aver de la metre a jorn.",
|
||||
"app_change_url_no_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, benlèu que vos cal la metre a jorn.",
|
||||
"app_change_no_change_url_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, poiretz aver de l’actualizar.",
|
||||
"app_change_url_no_script": "L’aplicacion {app_name:s} pren pas en compte lo cambiament d’URL, benlèu que vos cal l’actualizar.",
|
||||
"app_make_default_location_already_used": "Impossible de configurar l’aplicacion « {app} » per defaut pel domeni {domain} perque es ja utilizat per l’aplicacion {other_app}",
|
||||
"app_location_install_failed": "Impossible d’installar l’aplicacion a aqueste emplaçament per causa de conflicte amb l’aplicacion {other_app} qu’es ja installada sus {other_path}",
|
||||
"app_location_unavailable": "Aquesta URL es pas disponibla o en conflicte amb una aplicacion existenta :\n{apps:s}",
|
||||
|
@ -148,7 +148,7 @@
|
|||
"domain_dyndns_invalid": "Domeni incorrècte per una utilizacion amb DynDNS",
|
||||
"domain_dyndns_root_unknown": "Domeni DynDNS màger desconegut",
|
||||
"domain_exists": "Lo domeni existís ja",
|
||||
"domain_hostname_failed": "Fracàs de la creacion d’un nòu nom d’òst",
|
||||
"domain_hostname_failed": "Fracàs de la creacion d’un nòu nom d’òst. Aquò poirà provocar de problèmas mai tard (mas es pas segur… benlèu que coparà pas res).",
|
||||
"domain_unknown": "Domeni desconegut",
|
||||
"domain_zone_exists": "Lo fichièr zòna DNS existís ja",
|
||||
"domain_zone_not_found": "Fichèr de zòna DNS introbable pel domeni {:s}",
|
||||
|
@ -157,7 +157,7 @@
|
|||
"downloading": "Telecargament…",
|
||||
"dyndns_could_not_check_provide": "Impossible de verificar se {provider:s} pòt provesir {domain:s}.",
|
||||
"dyndns_cron_installed": "La tasca cron pel domeni DynDNS es installada",
|
||||
"dyndns_cron_remove_failed": "Impossible de levar la tasca cron pel domeni DynDNS",
|
||||
"dyndns_cron_remove_failed": "Impossible de levar la tasca cron pel domeni DynDNS a causa de {error}",
|
||||
"dyndns_cron_removed": "La tasca cron pel domeni DynDNS es levada",
|
||||
"dyndns_ip_update_failed": "Impossible d’actualizar l’adreça IP sul domeni DynDNS",
|
||||
"dyndns_ip_updated": "Vòstra adreça IP es estada actualizada pel domeni DynDNS",
|
||||
|
@ -180,8 +180,8 @@
|
|||
"invalid_url_format": "Format d’URL pas valid",
|
||||
"ldap_initialized": "L’annuari LDAP es inicializat",
|
||||
"license_undefined": "indefinida",
|
||||
"maindomain_change_failed": "Modificacion impossibla del domeni màger",
|
||||
"maindomain_changed": "Lo domeni màger es estat modificat",
|
||||
"main_domain_change_failed": "Modificacion impossibla del domeni màger",
|
||||
"main_domain_changed": "Lo domeni màger es estat modificat",
|
||||
"migrate_tsig_end": "La migracion cap a hmac-sha512 es acabada",
|
||||
"migrate_tsig_wait_2": "2 minutas…",
|
||||
"migrate_tsig_wait_3": "1 minuta…",
|
||||
|
@ -198,7 +198,7 @@
|
|||
"migrations_current_target": "La cibla de migracion est {}",
|
||||
"migrations_error_failed_to_load_migration": "ERROR : fracàs del cargament de la migracion {number} {name}",
|
||||
"migrations_list_conflict_pending_done": "Podètz pas utilizar --previous e --done a l’encòp.",
|
||||
"migrations_loading_migration": "Cargament de la migracion{number} {name}…",
|
||||
"migrations_loading_migration": "Cargament de la migracion {id}…",
|
||||
"migrations_no_migrations_to_run": "Cap de migracion de lançar",
|
||||
"migrations_show_currently_running_migration": "Realizacion de la migracion {number} {name}…",
|
||||
"migrations_show_last_migration": "La darrièra migracion realizada es {}",
|
||||
|
@ -264,7 +264,7 @@
|
|||
"migrate_tsig_failed": "La migracion del domeni dyndns {domain} cap a hmac-sha512 a pas capitat, anullacion de las modificacions. Error : {error_code} - {error}",
|
||||
"migrate_tsig_wait": "Esperem 3 minutas que lo servidor dyndns prenga en compte la novèla clau…",
|
||||
"migrate_tsig_not_needed": "Sembla qu’utilizatz pas un domeni dyndns, donc cap de migracion es pas necessària !",
|
||||
"migration_0003_yunohost_upgrade": "Aviada de la mesa a nivèl del paquet YunoHost… La migracion acabarà, mas la mesa a jorn reala se realizarà tot bèl aprèp. Un còp acabada, poiretz vos reconnectar a l’administracion web.",
|
||||
"migration_0003_yunohost_upgrade": "Aviada de la mesa a nivèl del paquet YunoHost… La migracion acabarà, mas l’actualizacion reala se realizarà tot bèl aprèp. Un còp acabada, poiretz vos reconnectar a l’administracion web.",
|
||||
"migration_0003_system_not_fully_up_to_date": "Lo sistèma es pas complètament a jorn. Mercés de lançar una mesa a jorn classica abans de començar la migracion per Stretch.",
|
||||
"migration_0003_modified_files": "Mercés de notar que los fichièrs seguents son estats detectats coma modificats manualament e poiràn èsser escafats a la fin de la mesa a nivèl : {manually_modified_files}",
|
||||
"monitor_period_invalid": "Lo periòde de temps es incorrècte",
|
||||
|
@ -376,10 +376,10 @@
|
|||
"migration_0003_general_warning": "Notatz qu’aquesta migracion es una operacion delicata. Encara que la còla YunoHost aguèsse fach çò melhor per la tornar legir e provar, la migracion poiriá copar de parts del sistèma o de las aplicacions.\n\nEn consequéncia, vos recomandam :\n· · · · - de lançar una salvagarda de vòstras donadas o aplicacions criticas. Mai d’informacions a https://yunohost.org/backup ;\n· · · · - d’èsser pacient aprèp aver lançat la migracion : segon vòstra connexion Internet e material, pòt trigar qualques oras per que tot siá mes al nivèl.\n\nEn mai, lo pòrt per SMTP, utilizat pels clients de corrièls extèrns (coma Thunderbird o K9-Mail per exemple) foguèt cambiat de 465 (SSL/TLS) per 587 (STARTTLS). L’ancian pòrt 465 serà automaticament tampat e lo nòu pòrt 587 serà dobèrt dins lo parafuòc. Vosautres e vòstres utilizaires *auretz* d’adaptar la configuracion de vòstre client de corrièl segon aqueles cambiaments !",
|
||||
"migration_0003_problematic_apps_warning": "Notatz que las aplicacions seguentas, saique problematicas, son estadas desactivadas. Semblan d’aver estadas installadas d’una lista d’aplicacions o que son pas marcadas coma «working ». En consequéncia, podèm pas assegurar que tendràn de foncionar aprèp la mesa a nivèl : {problematic_apps}",
|
||||
"migrations_bad_value_for_target": "Nombre invalid pel paramètre « target », los numèros de migracion son 0 o {}",
|
||||
"migrations_migration_has_failed": "La migracion {number} {name} a pas capitat amb l’excepcion {exception}, anullacion",
|
||||
"migrations_skip_migration": "Passatge de la migracion {number} {name}…",
|
||||
"migrations_to_be_ran_manually": "La migracion {number} {name} deu èsser lançada manualament. Mercés d’anar a Aisinas > Migracion dins l’interfàcia admin, o lançar « yunohost tools migrations migrate ».",
|
||||
"migrations_need_to_accept_disclaimer": "Per lançar la migracion {number} {name} , avètz d’acceptar aquesta clausa de non-responsabilitat :\n---\n{disclaimer}\n---\nS’acceptatz de lançar la migracion, mercés de tornar executar la comanda amb l’opcion accept-disclaimer.",
|
||||
"migrations_migration_has_failed": "La migracion {id} a pas capitat, abandon. Error : {exception}",
|
||||
"migrations_skip_migration": "Passatge de la migracion {id}…",
|
||||
"migrations_to_be_ran_manually": "La migracion {id} deu èsser lançada manualament. Mercés d’anar a Aisinas > Migracion dins l’interfàcia admin, o lançar « yunohost tools migrations migrate ».",
|
||||
"migrations_need_to_accept_disclaimer": "Per lançar la migracion {id} , avètz d’acceptar aquesta clausa de non-responsabilitat :\n---\n{disclaimer}\n---\nS’acceptatz de lançar la migracion, mercés de tornar executar la comanda amb l’opcion accept-disclaimer.",
|
||||
"monitor_disabled": "La supervision del servidor es desactivada",
|
||||
"monitor_enabled": "La supervision del servidor es activada",
|
||||
"mysql_db_initialized": "La basa de donadas MySQL es estada inicializada",
|
||||
|
@ -404,7 +404,7 @@
|
|||
"backup_no_uncompress_archive_dir": "Lo repertòri de l’archiu descomprimit existís pas",
|
||||
"pattern_username": "Deu èsser compausat solament de caractèrs alfanumerics en letras minusculas e de tirets basses",
|
||||
"experimental_feature": "Atencion : aquesta foncionalitat es experimentala e deu pas èsser considerada coma establa, deuriatz pas l’utilizar levat que sapiatz çò que fasètz.",
|
||||
"log_corrupted_md_file": "Lo fichièr yaml de metadonada amb los jornals d’audit es corromput : « {md_file} »",
|
||||
"log_corrupted_md_file": "Lo fichièr yaml de metadonada amb los jornals d’audit es corromput : « {md_file} »\nError : {error:s}",
|
||||
"log_category_404": "La categoria de jornals d’audit « {category} » existís pas",
|
||||
"log_link_to_log": "Jornal complèt d’aquesta operacion : <a href=\"#/tools/logs/{name}\" style=\"text-decoration:underline\">{desc}</a>",
|
||||
"log_help_to_get_log": "Per veire lo jornal d’aquesta operacion « {desc} », utilizatz la comanda « yunohost log display {name} »",
|
||||
|
@ -421,7 +421,7 @@
|
|||
"log_app_change_url": "Cambiar l’URL de l’aplicacion « {} »",
|
||||
"log_app_install": "Installar l’aplicacion « {} »",
|
||||
"log_app_remove": "Levar l’aplicacion « {} »",
|
||||
"log_app_upgrade": "Metre a jorn l’aplicacion « {} »",
|
||||
"log_app_upgrade": "Actualizacion de l’aplicacion « {} »",
|
||||
"log_app_makedefault": "Far venir « {} » l’aplicacion per defaut",
|
||||
"log_available_on_yunopaste": "Lo jornal es ara disponible via {url}",
|
||||
"log_backup_restore_system": "Restaurar lo sistèma a partir d’una salvagarda",
|
||||
|
@ -431,7 +431,7 @@
|
|||
"log_domain_add": "Ajustar lo domeni « {} » dins la configuracion sistèma",
|
||||
"log_domain_remove": "Tirar lo domeni « {} » d’a la configuracion sistèma",
|
||||
"log_dyndns_subscribe": "S’abonar al subdomeni YunoHost « {} »",
|
||||
"log_dyndns_update": "Metre a jorn l’adreça IP ligada a vòstre jos-domeni YunoHost « {} »",
|
||||
"log_dyndns_update": "Actualizar l’adreça IP ligada a vòstre jos-domeni YunoHost « {} »",
|
||||
"log_letsencrypt_cert_install": "Installar lo certificat Let's encrypt sul domeni « {} »",
|
||||
"log_selfsigned_cert_install": "Installar lo certificat auto-signat sul domeni « {} »",
|
||||
"log_letsencrypt_cert_renew": "Renovar lo certificat Let's encrypt de « {} »",
|
||||
|
@ -439,12 +439,12 @@
|
|||
"log_service_regen_conf": "Regenerar la configuracion sistèma de « {} »",
|
||||
"log_user_create": "Ajustar l’utilizaire « {} »",
|
||||
"log_user_delete": "Levar l’utilizaire « {} »",
|
||||
"log_user_update": "Metre a jorn las informacions a l’utilizaire « {} »",
|
||||
"log_tools_maindomain": "Far venir « {} » lo domeni màger",
|
||||
"log_user_update": "Actualizar las informacions a l’utilizaire « {} »",
|
||||
"log_domain_main_domain": "Far venir « {} » lo domeni màger",
|
||||
"log_tools_migrations_migrate_forward": "Migrar",
|
||||
"log_tools_migrations_migrate_backward": "Tornar en arrièr",
|
||||
"log_tools_postinstall": "Realizar la post installacion del servidor YunoHost",
|
||||
"log_tools_upgrade": "Mesa a jorn dels paquets sistèma",
|
||||
"log_tools_upgrade": "Actualizacion dels paquets sistèma",
|
||||
"log_tools_shutdown": "Atudar lo servidor",
|
||||
"log_tools_reboot": "Reaviar lo servidor",
|
||||
"mail_unavailable": "Aquesta adreça electronica es reservada e deu èsser automaticament atribuida al tot bèl just primièr utilizaire",
|
||||
|
@ -453,7 +453,7 @@
|
|||
"migration_0005_postgresql_94_not_installed": "Postgresql es pas installat sul sistèma. Pas res de far !",
|
||||
"migration_0005_postgresql_96_not_installed": "Avèm trobat que Postgresql 9.4 es installat, mas cap de version de Postgresql 9.6 pas trobada !? Quicòm d’estranh a degut arribar a vòstre sistèma :( …",
|
||||
"migration_0005_not_enough_space": "I a pas pro d’espaci disponible sus {path} per lançar la migracion d’aquela passa :(.",
|
||||
"recommend_to_add_first_user": "La post installacion es acabada, mas YunoHost fa besonh d’almens un utilizaire per foncionar coma cal. Vos cal n’ajustar un en utilizant la comanda « yunohost user create $username » o ben l’interfàcia d’administracion.",
|
||||
"recommend_to_add_first_user": "La post installacion es acabada, mas YunoHost fa besonh d’almens un utilizaire per foncionar coma cal. Vos cal n’ajustar un en utilizant la comanda « yunohost user create <username>' » o ben l’interfàcia d’administracion.",
|
||||
"service_description_php7.0-fpm": "executa d’aplicacions escrichas en PHP amb nginx",
|
||||
"users_available": "Lista dels utilizaires disponibles :",
|
||||
"good_practices_about_admin_password": "Sètz per definir un nòu senhal per l’administracion. Lo senhal deu almens conténer 8 caractèrs - encara que siá de bon far d’utilizar un senhal mai long qu’aquò (ex. una passafrasa) e/o d’utilizar mantun tipes de caractèrs (majuscula, minuscula, nombre e caractèrs especials).",
|
||||
|
@ -468,12 +468,12 @@
|
|||
"password_too_simple_4": "Lo senhal deu conténer almens 12 caractèrs, de nombre, majusculas, minisculas e caractèrs specials",
|
||||
"root_password_desynchronized": "Lo senhal de l’administrator es estat cambiat, mas YunoHost a pas pogut l’espandir al senhal root !",
|
||||
"aborting": "Interrupcion.",
|
||||
"app_not_upgraded": "Las aplicacions seguentas son pas estadas actualizadas : {apps}",
|
||||
"app_not_upgraded": "L’aplicacion « {failed_app} » a pas reüssit a s’actualizar e coma consequéncia las mesas a jorn de las aplicacions seguentas son estadas anulladas : {apps}",
|
||||
"app_start_install": "Installacion de l’aplicacion {app}…",
|
||||
"app_start_remove": "Supression de l’aplicacion {app}…",
|
||||
"app_start_backup": "Recuperacion dels fichièrs de salvagardar per {app}…",
|
||||
"app_start_restore": "Restauracion de l’aplicacion {app}…",
|
||||
"app_upgrade_several_apps": "Las aplicacions seguentas seràn mesas a jorn : {apps}",
|
||||
"app_upgrade_several_apps": "Las aplicacions seguentas seràn actualizadas : {apps}",
|
||||
"ask_new_domain": "Nòu domeni",
|
||||
"ask_new_path": "Nòu camin",
|
||||
"backup_actually_backuping": "Creacion d’un archiu de seguretat a partir dels fichièrs recuperats…",
|
||||
|
@ -547,5 +547,70 @@
|
|||
"tools_upgrade_special_packages": "Actualizacion dels paquets « especials » (ligats a YunoHost)…",
|
||||
"tools_upgrade_special_packages_explanation": "Aquesta accion s’acabarà mas l’actualizacion especiala actuala contunharà en rèire-plan. Comencetz pas cap d’autra accion sul servidor dins las ~ 10 minutas que venon (depend de la velocitat de la maquina). Un còp acabat, benlèu que vos calrà vos tornar connectar a l’interfàcia d’administracion. Los jornals d’audit de l’actualizacion seràn disponibles a Aisinas > Jornals d’audit (dins l’interfàcia d’administracion) o amb « yunohost log list » (en linha de comanda).",
|
||||
"update_apt_cache_failed": "I a agut d’errors en actualizar la memòria cache d’APT (lo gestionari de paquets de Debian). Aquí avètz las linhas de sources.list que pòdon vos ajudar a identificar las linhas problematicas : \n{sourceslist}",
|
||||
"update_apt_cache_warning": "I a agut d’errors en actualizar la memòria cache d’APT (lo gestionari de paquets de Debian). Aquí avètz las linhas de sources.list que pòdon vos ajudar a identificar las linhas problematicas : \n{sourceslist}"
|
||||
"update_apt_cache_warning": "I a agut d’errors en actualizar la memòria cache d’APT (lo gestionari de paquets de Debian). Aquí avètz las linhas de sources.list que pòdon vos ajudar a identificar las linhas problematicas : \n{sourceslist}",
|
||||
"apps_permission_not_found": "Cap d’autorizacion pas trobada per las aplicacions installadas",
|
||||
"backup_permission": "Autorizacion de salvagarda per l’aplicacion {app:s}",
|
||||
"edit_group_not_allowed": "Sètz pas autorizat a cambiar lo grop {group:s}",
|
||||
"error_when_removing_sftpuser_group": "Error en ensajar de suprimir lo grop sftpusers",
|
||||
"group_name_already_exist": "Lo grop {name:s} existís ja",
|
||||
"group_created": "Lo grop « {group} » es estat corrèctament creat",
|
||||
"group_creation_failed": "Fracàs de la creacion del grop « {group} »",
|
||||
"group_deleted": "Lo grop « {group} » es estat suprimit",
|
||||
"group_deletion_failed": "Fracàs de la supression del grop « {group} »",
|
||||
"group_deletion_not_allowed": "Lo grop « {group} » pòt pas èsser suprimir manualament.",
|
||||
"group_info_failed": "Recuperacion de las informacions del grop « {group} » impossibla",
|
||||
"group_unknown": "Lo grop « {group} » es desconegut",
|
||||
"log_user_group_add": "Ajustar lo grop « {} »",
|
||||
"log_user_group_delete": "Suprimir lo grop « {} »",
|
||||
"migration_0011_backup_before_migration": "Creacion d’una còpia de seguretat de la basa de donadas LDAP e de la configuracion de las aplicacions abans d’efectuar la migracion.",
|
||||
"migration_0011_create_group": "Creacion d’un grop per cada utilizaire…",
|
||||
"migration_0011_done": "Migracion complèta. Ara podètz gerir de grops d’utilizaires.",
|
||||
"migration_0011_LDAP_update_failed": "Actualizacion impossibla de LDAP. Error : {error:s}",
|
||||
"migration_0011_migration_failed_trying_to_rollback": "La migracion a fracassat… ensag de tornar lo sistèma a l’estat anterio.",
|
||||
"migration_0011_rollback_success": "Restauracion del sistèma reüssida.",
|
||||
"apps_permission_restoration_failed": "Fracàs de la permission « {permission:s} » per la restauracion de l’aplicacion {app:s}",
|
||||
"group_already_allowed": "Lo grop « {group:s} » a ja la permission « {permission:s} » activada per l’aplicacion « {app:s} »",
|
||||
"group_already_disallowed": "Lo grop « {group:s} »a ja las permissions « {permission:s} » desactivadas per l’aplicacion « {app:s} »",
|
||||
"group_updated": "Lo grop « {group} » es estat actualizat",
|
||||
"group_update_failed": "Actualizacion impossibla del grop « {group} »",
|
||||
"log_permission_add": "Ajustar la permission « {} » per l’aplicacion « {} »",
|
||||
"log_permission_remove": "Suprimir la permission « {} »",
|
||||
"log_permission_update": "Actualizacion de la permission « {} » per l’aplicacion « {} »",
|
||||
"log_user_group_update": "Actualizar lo grop « {} »",
|
||||
"log_user_permission_add": "Actualizar la permission « {} »",
|
||||
"log_user_permission_remove": "Actualizar la permission « {} »",
|
||||
"migration_description_0011_setup_group_permission": "Configurar lo grop d’utilizaire e las permission de las aplicacions e dels servicis",
|
||||
"migration_0011_can_not_backup_before_migration": "La salvagarda del sistèma abans la migracion a pas capitat. La migracion a fracassat. Error : {error:s}",
|
||||
"migration_0011_migrate_permission": "Migracion de las permission dels paramètres d’aplicacion a LDAP…",
|
||||
"migration_0011_update_LDAP_database": "Actualizacion de la basa de donadas LDAP…",
|
||||
"migration_0011_update_LDAP_schema": "Actualizacion de l’esquèma LDAP…",
|
||||
"permission_already_exist": "La permission « {permission:s} » per l’aplicacion {app:s} existís ja",
|
||||
"permission_created": "Permission creada « {permission:s} » per l’aplicacion{app:s}",
|
||||
"permission_creation_failed": "Creacion impossibla de la permission",
|
||||
"permission_deleted": "Permission « {permission:s} » per l’aplicacion {app:s} suprimida",
|
||||
"permission_deletion_failed": "Fracàs de la supression de la permission « {permission:s} » per l’aplicacion {app:s}",
|
||||
"permission_not_found": "Permission « {permission:s} » pas trobada per l’aplicacion {app:s}",
|
||||
"permission_name_not_valid": "Lo nom de la permission « {permission:s} » es pas valid",
|
||||
"permission_update_failed": "Fracàs de l’actualizacion de la permission",
|
||||
"permission_generated": "La basa de donadas de las permission es estada actualizada",
|
||||
"permission_updated": "La permission « {permission:s} » per l’aplicacion {app:s} es estada actualizada",
|
||||
"permission_update_nothing_to_do": "Cap de permission d’actualizar",
|
||||
"remove_main_permission_not_allowed": "Se pòt pas suprimir la permission màger",
|
||||
"remove_user_of_group_not_allowed": "Sètz pas autorizat a suprimir {user:s} del grop {group:s}",
|
||||
"system_groupname_exists": "Lo nom del grop existís ja dins lo sistèma de grops",
|
||||
"tools_update_failed_to_app_fetchlist": "Fracàs de l’actualizacion de la lista d’aplicacions de YunoHost a causa de : {error}",
|
||||
"user_already_in_group": "L’utilizaire {user:} es ja dins lo grop {group:s}",
|
||||
"user_not_in_group": "L’utilizaire {user:} es pas dins lo grop {group:s}",
|
||||
"edit_permission_with_group_all_users_not_allowed": "Podètz pas modificar las permissions del grop « all_users », utilizatz « yunohost user permission clear APP » o « yunohost user permission add APP -u USER ».",
|
||||
"mailbox_disabled": "La bóstia de las letras es desactivada per l’utilizaire {user:s}",
|
||||
"migration_0011_LDAP_config_dirty": "Sembla qu’avètz modificat manualament la configuracion LDAP. Per far aquesta migracion cal actualizar la configuracion LDAP.\nSalvagardatz la configuracion actuala, reïnicializatz la configuracion originala amb la comanda « yunohost tools regen-conf -f » e tornatz ensajar la migracion",
|
||||
"need_define_permission_before": "Vos cal tornar definir las permission en utilizant « yunohost user permission add -u USER » abans de suprimir un grop permés",
|
||||
"permission_already_clear": "La permission « {permission:s} » ja levada per l’aplicacion {app:s}",
|
||||
"migration_description_0012_postgresql_password_to_md5_authentication": "Forçar l’autentificacion postgresql a utilizar md5 per las connexions localas",
|
||||
"migrations_success_forward": "Migracion {id} corrèctament realizada !",
|
||||
"migrations_running_forward": "Execucion de la migracion {id}…",
|
||||
"migrations_must_provide_explicit_targets": "Devètz fornir una cibla explicita quand utilizatz using --skip o --force-rerun",
|
||||
"migrations_exclusive_options": "--auto, --skip, e --force-rerun son las opcions exclusivas.",
|
||||
"migrations_failed_to_load_migration": "Cargament impossible de la migracion {id} : {error}",
|
||||
"migrations_already_ran": "Aquelas migracions s’executèron ja : {ids}"
|
||||
}
|
||||
|
|
|
@ -72,10 +72,10 @@
|
|||
"ldap_initialized": "LDAP inicializada com êxito",
|
||||
"license_undefined": "indefinido",
|
||||
"mail_alias_remove_failed": "Não foi possível remover a etiqueta de correio '{mail:s}'",
|
||||
"mail_domain_unknown": "Domínio de endereço de correio desconhecido '{domain:s}'",
|
||||
"mail_domain_unknown": "Domínio de endereço de correio '{domain:s}' inválido. Por favor, usa um domínio administrado per esse servidor.",
|
||||
"mail_forward_remove_failed": "Não foi possível remover o reencaminhamento de correio '{mail:s}'",
|
||||
"maindomain_change_failed": "Incapaz alterar o domínio raiz",
|
||||
"maindomain_changed": "Domínio raiz alterado com êxito",
|
||||
"main_domain_change_failed": "Incapaz alterar o domínio raiz",
|
||||
"main_domain_changed": "Domínio raiz alterado com êxito",
|
||||
"monitor_disabled": "Monitorização do servidor parada com êxito",
|
||||
"monitor_enabled": "Monitorização do servidor ativada com êxito",
|
||||
"monitor_glances_con_failed": "Não foi possível ligar ao servidor Glances",
|
||||
|
|
|
@ -1 +1,11 @@
|
|||
{}
|
||||
{
|
||||
"password_too_simple_1": "Lösenordet måste bestå av minst åtta tecken",
|
||||
"app_action_broke_system": "Åtgärden verkar ha fått följande viktiga tjänster att haverera: {services}",
|
||||
"already_up_to_date": "Ingenting att göra. Allt är redan uppdaterat.",
|
||||
"admin_password": "Administratörslösenord",
|
||||
"admin_password_too_long": "Välj gärna ett lösenord som inte innehåller fler än 127 tecken",
|
||||
"admin_password_change_failed": "Kan inte byta lösenord",
|
||||
"action_invalid": "Ej tillåten åtgärd '{action:s}'",
|
||||
"admin_password_changed": "Administratörskontots lösenord ändrades",
|
||||
"aborting": "Avbryter."
|
||||
}
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
{}
|
||||
{
|
||||
"password_too_simple_1": "Şifre en az 8 karakter uzunluğunda olmalı"
|
||||
}
|
||||
|
|
4
pytest.ini
Normal file
4
pytest.ini
Normal file
|
@ -0,0 +1,4 @@
|
|||
[pytest]
|
||||
addopts = -s -v
|
||||
norecursedirs = dist doc build .tox .eggs
|
||||
testpaths = tests/
|
2
setup.cfg
Normal file
2
setup.cfg
Normal file
|
@ -0,0 +1,2 @@
|
|||
[flake8]
|
||||
ignore = E501,E128,E731,E722
|
File diff suppressed because it is too large
Load diff
|
@ -40,7 +40,7 @@ from moulinette import msignals, m18n
|
|||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils import filesystem
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.filesystem import read_file, mkdir
|
||||
from moulinette.utils.filesystem import read_file, mkdir, write_to_yaml, read_yaml
|
||||
|
||||
from yunohost.app import (
|
||||
app_info, _is_installed, _parse_app_instance_name, _patch_php5, _patch_legacy_helpers
|
||||
|
@ -48,7 +48,6 @@ from yunohost.app import (
|
|||
from yunohost.hook import (
|
||||
hook_list, hook_info, hook_callback, hook_exec, CUSTOM_HOOK_FOLDER
|
||||
)
|
||||
from yunohost.monitor import binary_to_human
|
||||
from yunohost.tools import tools_postinstall
|
||||
from yunohost.regenconf import regen_conf
|
||||
from yunohost.log import OperationLogger
|
||||
|
@ -602,10 +601,10 @@ class BackupManager():
|
|||
env=env_dict,
|
||||
chdir=self.work_dir)
|
||||
|
||||
ret_succeed = {hook: {path:result["state"] for path, result in infos.items()}
|
||||
ret_succeed = {hook: [path for path, result in infos.items() if result["state"] == "succeed"]
|
||||
for hook, infos in ret.items()
|
||||
if any(result["state"] == "succeed" for result in infos.values())}
|
||||
ret_failed = {hook: {path:result["state"] for path, result in infos.items.items()}
|
||||
ret_failed = {hook: [path for path, result in infos.items.items() if result["state"] == "failed"]
|
||||
for hook, infos in ret.items()
|
||||
if any(result["state"] == "failed" for result in infos.values())}
|
||||
|
||||
|
@ -677,6 +676,8 @@ class BackupManager():
|
|||
backup_app_failed -- Raised at the end if the app backup script
|
||||
execution failed
|
||||
"""
|
||||
from yunohost.permission import user_permission_list
|
||||
|
||||
app_setting_path = os.path.join('/etc/yunohost/apps/', app)
|
||||
|
||||
# Prepare environment
|
||||
|
@ -704,8 +705,9 @@ class BackupManager():
|
|||
|
||||
# backup permissions
|
||||
logger.debug(m18n.n('backup_permission', app=app))
|
||||
ldap_url = "ldap:///dc=yunohost,dc=org???(&(objectClass=permissionYnh)(cn=*.%s))" % app
|
||||
os.system("slapcat -b dc=yunohost,dc=org -H '%s' -l '%s/permission.ldif'" % (ldap_url, settings_dir))
|
||||
permissions = user_permission_list(full=True)["permissions"]
|
||||
this_app_permissions = {name: infos for name, infos in permissions.items() if name.startswith(app + ".")}
|
||||
write_to_yaml("%s/permissions.yml" % settings_dir, this_app_permissions)
|
||||
|
||||
except:
|
||||
abs_tmp_app_dir = os.path.join(self.work_dir, 'apps/', app)
|
||||
|
@ -919,7 +921,7 @@ class RestoreManager():
|
|||
|
||||
successfull_apps = self.targets.list("apps", include=["Success", "Warning"])
|
||||
|
||||
permission_sync_to_user(force=False)
|
||||
permission_sync_to_user()
|
||||
|
||||
if os.path.ismount(self.work_dir):
|
||||
ret = subprocess.call(["umount", self.work_dir])
|
||||
|
@ -1131,6 +1133,8 @@ class RestoreManager():
|
|||
|
||||
self._restore_system()
|
||||
self._restore_apps()
|
||||
except Exception as e:
|
||||
raise YunohostError("The following critical error happened during restoration: %s" % e)
|
||||
finally:
|
||||
self.clean()
|
||||
|
||||
|
@ -1183,18 +1187,12 @@ class RestoreManager():
|
|||
if system_targets == []:
|
||||
return
|
||||
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
from yunohost.user import user_group_list
|
||||
from yunohost.permission import permission_create, permission_delete, user_permission_update, user_permission_list, permission_sync_to_user
|
||||
|
||||
# Backup old permission for apps
|
||||
# We need to do that because in case of an app is installed we can't remove the permission for this app
|
||||
old_apps_permission = []
|
||||
try:
|
||||
old_apps_permission = ldap.search('ou=permission,dc=yunohost,dc=org',
|
||||
'(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.metronome))(!(cn=main.sftp)))',
|
||||
['cn', 'objectClass', 'groupPermission', 'URL', 'gidNumber'])
|
||||
except:
|
||||
logger.info(m18n.n('apps_permission_not_found'))
|
||||
old_apps_permission = user_permission_list(ignore_system_perms=True, full=True)["permissions"]
|
||||
|
||||
# Start register change on system
|
||||
operation_logger = OperationLogger('backup_restore_system')
|
||||
|
@ -1232,12 +1230,11 @@ class RestoreManager():
|
|||
|
||||
regen_conf()
|
||||
|
||||
# Check if we need to do the migration 0009 : setup group and permission
|
||||
# Check that at least a group exists (all_users) to know if we need to
|
||||
# do the migration 0011 : setup group and permission
|
||||
#
|
||||
# Legacy code
|
||||
result = ldap.search('ou=groups,dc=yunohost,dc=org',
|
||||
'(&(objectclass=groupOfNamesYnh)(cn=all_users))',
|
||||
['cn'])
|
||||
if not result:
|
||||
if not "all_users" in user_group_list()["groups"].keys():
|
||||
from yunohost.tools import _get_migration_by_name
|
||||
setup_group_permission = _get_migration_by_name("setup_group_permission")
|
||||
# Update LDAP schema restart slapd
|
||||
|
@ -1245,24 +1242,17 @@ class RestoreManager():
|
|||
regen_conf(names=['slapd'], force=True)
|
||||
setup_group_permission.migrate_LDAP_db()
|
||||
|
||||
# Remove all permission for all app which sill in the LDAP
|
||||
for per in ldap.search('ou=permission,dc=yunohost,dc=org',
|
||||
'(&(objectClass=permissionYnh)(!(cn=main.mail))(!(cn=main.metronome))(!(cn=main.sftp)))',
|
||||
['cn']):
|
||||
if not ldap.remove('cn=%s,ou=permission' % per['cn'][0]):
|
||||
raise YunohostError('permission_deletion_failed',
|
||||
permission=per['cn'][0].split('.')[0],
|
||||
app=per['cn'][0].split('.')[1])
|
||||
# Remove all permission for all app which is still in the LDAP
|
||||
for permission_name in user_permission_list(ignore_system_perms=True)["permissions"].keys():
|
||||
permission_delete(permission_name, force=True, sync_perm=False)
|
||||
|
||||
# Restore permission for the app which is installed
|
||||
for per in old_apps_permission:
|
||||
try:
|
||||
permission_name, app_name = per['cn'][0].split('.')
|
||||
except:
|
||||
logger.warning(m18n.n('permission_name_not_valid', permission=per['cn'][0]))
|
||||
for permission_name, permission_infos in old_apps_permission.items():
|
||||
app_name = permission_name.split(".")[0]
|
||||
if _is_installed(app_name):
|
||||
if not ldap.add('cn=%s,ou=permission' % per['cn'][0], per):
|
||||
raise YunohostError('apps_permission_restoration_failed', permission=permission_name, app=app_name)
|
||||
permission_create(permission_name, url=permission_infos["url"], allowed=permission_infos["allowed"], sync_perm=False)
|
||||
|
||||
permission_sync_to_user()
|
||||
|
||||
|
||||
def _restore_apps(self):
|
||||
|
@ -1271,7 +1261,6 @@ class RestoreManager():
|
|||
apps_targets = self.targets.list("apps", exclude=["Skipped"])
|
||||
|
||||
for app in apps_targets:
|
||||
print(app)
|
||||
self._restore_app(app)
|
||||
|
||||
def _restore_app(self, app_instance_name):
|
||||
|
@ -1301,11 +1290,8 @@ class RestoreManager():
|
|||
name already exists
|
||||
restore_app_failed -- Raised if the restore bash script failed
|
||||
"""
|
||||
from moulinette.utils.filesystem import read_ldif
|
||||
from yunohost.user import user_group_list
|
||||
from yunohost.permission import permission_remove
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
from yunohost.permission import permission_create, permission_delete, user_permission_list, user_permission_update, permission_sync_to_user
|
||||
|
||||
def copytree(src, dst, symlinks=False, ignore=None):
|
||||
for item in os.listdir(src):
|
||||
|
@ -1373,22 +1359,27 @@ class RestoreManager():
|
|||
restore_script = os.path.join(tmp_folder_for_app_restore, 'restore')
|
||||
|
||||
# Restore permissions
|
||||
if os.path.isfile(app_settings_in_archive + '/permission.ldif'):
|
||||
filtred_entries = ['entryUUID', 'creatorsName', 'createTimestamp', 'entryCSN', 'structuralObjectClass',
|
||||
'modifiersName', 'modifyTimestamp', 'inheritPermission', 'memberUid']
|
||||
entries = read_ldif('%s/permission.ldif' % app_settings_in_archive, filtred_entries)
|
||||
group_list = user_group_list(['cn'])['groups']
|
||||
for dn, entry in entries:
|
||||
# Remove the group which has been removed
|
||||
for group in entry['groupPermission']:
|
||||
group_name = group.split(',')[0].split('=')[1]
|
||||
if group_name not in group_list:
|
||||
entry['groupPermission'].remove(group)
|
||||
if not ldap.add('cn=%s,ou=permission' % entry['cn'][0], entry):
|
||||
raise YunohostError('apps_permission_restoration_failed',
|
||||
permission=entry['cn'][0].split('.')[0],
|
||||
app=entry['cn'][0].split('.')[1])
|
||||
if os.path.isfile('%s/permissions.yml' % app_settings_new_path):
|
||||
|
||||
permissions = read_yaml('%s/permissions.yml' % app_settings_new_path)
|
||||
existing_groups = user_group_list()['groups']
|
||||
|
||||
for permission_name, permission_infos in permissions.items():
|
||||
|
||||
if "allowed" not in permission_infos:
|
||||
logger.warning("'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s … You might have to reconfigure permissions yourself." % (permission_name, app_instance_name))
|
||||
should_be_allowed = ["all_users"]
|
||||
else:
|
||||
should_be_allowed = [g for g in permission_infos["allowed"] if g in existing_groups]
|
||||
|
||||
permission_create(permission_name, url=permission_infos.get("url", None), allowed=should_be_allowed, sync_perm=False)
|
||||
|
||||
permission_sync_to_user()
|
||||
|
||||
os.remove('%s/permissions.yml' % app_settings_new_path)
|
||||
else:
|
||||
# Otherwise, we need to migrate the legacy permissions of this
|
||||
# app (included in its settings.yml)
|
||||
from yunohost.tools import _get_migration_by_name
|
||||
setup_group_permission = _get_migration_by_name("setup_group_permission")
|
||||
setup_group_permission.migrate_app_permission(app=app_instance_name)
|
||||
|
@ -1427,7 +1418,6 @@ class RestoreManager():
|
|||
operation_logger.start()
|
||||
|
||||
# Execute remove script
|
||||
# TODO: call app_remove instead
|
||||
if hook_exec(remove_script, args=[app_instance_name],
|
||||
env=env_dict_remove)[0] != 0:
|
||||
msg = m18n.n('app_not_properly_removed', app=app_instance_name)
|
||||
|
@ -1439,12 +1429,10 @@ class RestoreManager():
|
|||
# Cleaning app directory
|
||||
shutil.rmtree(app_settings_new_path, ignore_errors=True)
|
||||
|
||||
# Remove all permission in LDAP
|
||||
result = ldap.search(base='ou=permission,dc=yunohost,dc=org',
|
||||
filter='(&(objectclass=permissionYnh)(cn=*.%s))' % app_instance_name, attrs=['cn'])
|
||||
permission_list = [p['cn'][0] for p in result]
|
||||
for l in permission_list:
|
||||
permission_remove(app_instance_name, l.split('.')[0], force=True)
|
||||
# Remove all permission in LDAP for this app
|
||||
for permission_name in user_permission_list()["permissions"].keys():
|
||||
if permission_name.startswith(app_instance_name+"."):
|
||||
permission_delete(permission_name, force=True)
|
||||
|
||||
# TODO Cleaning app hooks
|
||||
else:
|
||||
|
@ -2387,6 +2375,15 @@ def backup_info(name, with_details=False, human_readable=False):
|
|||
if "size_details" in info.keys():
|
||||
for category in ["apps", "system"]:
|
||||
for name, key_info in info[category].items():
|
||||
|
||||
if category == "system":
|
||||
# Stupid legacy fix for weird format between 3.5 and 3.6
|
||||
if isinstance(key_info, dict):
|
||||
key_info = key_info.keys()
|
||||
info[category][name] = key_info = {"paths": key_info}
|
||||
else:
|
||||
info[category][name] = key_info
|
||||
|
||||
if name in info["size_details"][category].keys():
|
||||
key_info["size"] = info["size_details"][category][name]
|
||||
if human_readable:
|
||||
|
@ -2497,3 +2494,23 @@ def disk_usage(path):
|
|||
|
||||
du_output = subprocess.check_output(['du', '-sb', path])
|
||||
return int(du_output.split()[0].decode('utf-8'))
|
||||
|
||||
|
||||
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%s' % (value, s)
|
||||
return "%s" % n
|
||||
|
|
|
@ -10,10 +10,6 @@ class MyMigration(Migration):
|
|||
|
||||
all_certificate_files = glob.glob("/etc/yunohost/certs/*/*.pem")
|
||||
|
||||
def forward(self):
|
||||
def run(self):
|
||||
for filename in self.all_certificate_files:
|
||||
chown(filename, uid="root", gid="ssl-cert")
|
||||
|
||||
def backward(self):
|
||||
for filename in self.all_certificate_files:
|
||||
chown(filename, uid="root", gid="metronome")
|
||||
|
|
|
@ -19,11 +19,7 @@ class MyMigration(Migration):
|
|||
|
||||
"Migrate Dyndns stuff from MD5 TSIG to SHA512 TSIG"
|
||||
|
||||
def backward(self):
|
||||
# Not possible because that's a non-reversible operation ?
|
||||
pass
|
||||
|
||||
def migrate(self, dyn_host="dyndns.yunohost.org", domain=None, private_key_path=None):
|
||||
def run(self, dyn_host="dyndns.yunohost.org", domain=None, private_key_path=None):
|
||||
|
||||
if domain is None or private_key_path is None:
|
||||
try:
|
||||
|
|
|
@ -29,11 +29,7 @@ class MyMigration(Migration):
|
|||
|
||||
mode = "manual"
|
||||
|
||||
def backward(self):
|
||||
|
||||
raise YunohostError("migration_0003_backward_impossible")
|
||||
|
||||
def migrate(self):
|
||||
def run(self):
|
||||
|
||||
self.logfile = "/var/log/yunohost/{}.log".format(self.name)
|
||||
|
||||
|
|
|
@ -22,8 +22,9 @@ class MyMigration(Migration):
|
|||
|
||||
"Migrate php5-fpm 'pool' conf files to php7 stuff"
|
||||
|
||||
def migrate(self):
|
||||
dependencies = ["migrate_to_stretch"]
|
||||
|
||||
def run(self):
|
||||
# Get list of php5 pool files
|
||||
php5_pool_files = glob.glob("{}/*.conf".format(PHP5_POOLS))
|
||||
|
||||
|
|
|
@ -14,7 +14,9 @@ class MyMigration(Migration):
|
|||
|
||||
"Migrate DBs from Postgresql 9.4 to 9.6 after migrating to Stretch"
|
||||
|
||||
def migrate(self):
|
||||
dependencies = ["migrate_to_stretch"]
|
||||
|
||||
def run(self):
|
||||
|
||||
if not self.package_is_installed("postgresql-9.4"):
|
||||
logger.warning(m18n.n("migration_0005_postgresql_94_not_installed"))
|
||||
|
@ -32,10 +34,6 @@ class MyMigration(Migration):
|
|||
subprocess.check_call("pg_dropcluster --stop 9.4 main", shell=True)
|
||||
subprocess.check_call("service postgresql start", shell=True)
|
||||
|
||||
def backward(self):
|
||||
|
||||
pass
|
||||
|
||||
def package_is_installed(self, package_name):
|
||||
|
||||
p = subprocess.Popen("dpkg --list | grep '^ii ' | grep -q -w {}".format(package_name), shell=True)
|
||||
|
|
|
@ -20,16 +20,13 @@ class MyMigration(Migration):
|
|||
|
||||
"Synchronize admin and root passwords"
|
||||
|
||||
def migrate(self):
|
||||
def run(self):
|
||||
|
||||
new_hash = self._get_admin_hash()
|
||||
self._replace_root_hash(new_hash)
|
||||
|
||||
logger.info(m18n.n("root_password_replaced_by_admin_password"))
|
||||
|
||||
def backward(self):
|
||||
pass
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ class MyMigration(Migration):
|
|||
use the recommended configuration, with an appropriate disclaimer.
|
||||
"""
|
||||
|
||||
def migrate(self):
|
||||
def run(self):
|
||||
|
||||
# Check if deprecated DSA Host Key is in config
|
||||
dsa_rgx = r'^[ \t]*HostKey[ \t]+/etc/ssh/ssh_host_dsa_key[ \t]*(?:#.*)?$'
|
||||
|
@ -60,14 +60,11 @@ class MyMigration(Migration):
|
|||
regen_conf(names=['ssh'], force=True)
|
||||
copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF)
|
||||
|
||||
# Restart ssh and backward if it fail
|
||||
# Restart ssh and rollback if it failed
|
||||
if not _run_service_command('restart', 'ssh'):
|
||||
self.backward()
|
||||
raise YunohostError("migration_0007_cancel")
|
||||
|
||||
def backward(self):
|
||||
|
||||
# We don't backward completely but it should be enough
|
||||
copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF)
|
||||
if not _run_service_command('restart', 'ssh'):
|
||||
raise YunohostError("migration_0007_cannot_restart")
|
||||
# We don't rollback completely but it should be enough
|
||||
copyfile('/etc/ssh/sshd_config.bkp', SSHD_CONF)
|
||||
if not _run_service_command('restart', 'ssh'):
|
||||
raise YunohostError("migration_0007_cannot_restart")
|
||||
else:
|
||||
raise YunohostError("migration_0007_cancelled")
|
||||
|
|
|
@ -33,7 +33,9 @@ class MyMigration(Migration):
|
|||
shown - and the user may also choose to skip this migration.
|
||||
"""
|
||||
|
||||
def migrate(self):
|
||||
dependencies = ["ssh_conf_managed_by_yunohost_step1"]
|
||||
|
||||
def run(self):
|
||||
settings_set("service.ssh.allow_deprecated_dsa_hostkey", False)
|
||||
regen_conf(names=['ssh'], force=True)
|
||||
|
||||
|
@ -42,10 +44,6 @@ class MyMigration(Migration):
|
|||
if os.path.isdir(ARCHIVES_PATH):
|
||||
chown(ARCHIVES_PATH, uid="admin", gid="root")
|
||||
|
||||
def backward(self):
|
||||
|
||||
raise YunohostError("migration_0008_backward_impossible")
|
||||
|
||||
@property
|
||||
def mode(self):
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ class MyMigration(Migration):
|
|||
Decouple the regen conf mechanism from the concept of services
|
||||
"""
|
||||
|
||||
def migrate(self):
|
||||
def run(self):
|
||||
|
||||
if "conffiles" not in read_file("/etc/yunohost/services.yml") \
|
||||
or os.path.exists(REGEN_CONF_FILE):
|
||||
|
@ -37,6 +37,3 @@ class MyMigration(Migration):
|
|||
# (Actually save the modification of services)
|
||||
_save_services(services)
|
||||
|
||||
def backward(self):
|
||||
|
||||
pass
|
||||
|
|
|
@ -15,7 +15,7 @@ class MyMigration(Migration):
|
|||
|
||||
"Migrate from official.json to apps.json"
|
||||
|
||||
def migrate(self):
|
||||
def run(self):
|
||||
|
||||
# Backup current app list json
|
||||
os.system("cp %s %s" % (APPSLISTS_JSON, APPSLISTS_BACKUP))
|
||||
|
@ -28,17 +28,21 @@ class MyMigration(Migration):
|
|||
"labriqueinter.net/apps/labriqueinternet.json",
|
||||
"labriqueinter.net/internetcube.json"
|
||||
]
|
||||
try:
|
||||
appslists = _read_appslist_list()
|
||||
for appslist, infos in appslists.items():
|
||||
if infos["url"].split("//")[-1] in lists_to_remove:
|
||||
app_removelist(name=appslist)
|
||||
|
||||
appslists = _read_appslist_list()
|
||||
for appslist, infos in appslists.items():
|
||||
if infos["url"].split("//")[-1] in lists_to_remove:
|
||||
app_removelist(name=appslist)
|
||||
# Replace by apps.json list
|
||||
app_fetchlist(name="yunohost",
|
||||
url="https://app.yunohost.org/apps.json")
|
||||
except Exception:
|
||||
if os.path.exists(APPSLISTS_BACKUP):
|
||||
os.system("cp %s %s" % (APPSLISTS_BACKUP, APPSLISTS_JSON))
|
||||
raise
|
||||
else:
|
||||
if os.path.exists(APPSLISTS_BACKUP):
|
||||
os.remove(APPSLISTS_BACKUP)
|
||||
|
||||
# Replace by apps.json list
|
||||
app_fetchlist(name="yunohost",
|
||||
url="https://app.yunohost.org/apps.json")
|
||||
|
||||
def backward(self):
|
||||
|
||||
if os.path.exists(APPSLISTS_BACKUP):
|
||||
os.system("cp %s %s" % (APPSLISTS_BACKUP, APPSLISTS_JSON))
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import yaml
|
||||
import time
|
||||
import os
|
||||
|
||||
from moulinette import m18n
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.filesystem import read_yaml
|
||||
|
||||
from yunohost.tools import Migration
|
||||
from yunohost.user import user_group_add, user_group_update
|
||||
from yunohost.user import user_list, user_group_create, user_group_update
|
||||
from yunohost.app import app_setting, app_list
|
||||
from yunohost.regenconf import regen_conf
|
||||
from yunohost.permission import permission_add, permission_sync_to_user
|
||||
from yunohost.user import user_permission_add
|
||||
from yunohost.regenconf import regen_conf, BACKUP_CONF_DIR
|
||||
from yunohost.permission import permission_create, user_permission_update, permission_sync_to_user
|
||||
|
||||
logger = getActionLogger('yunohost.migration')
|
||||
|
||||
|
@ -19,6 +18,7 @@ logger = getActionLogger('yunohost.migration')
|
|||
# Tools used also for restoration
|
||||
###################################################
|
||||
|
||||
|
||||
class MyMigration(Migration):
|
||||
"""
|
||||
Update the LDAP DB to be able to store the permission
|
||||
|
@ -28,6 +28,28 @@ class MyMigration(Migration):
|
|||
|
||||
required = True
|
||||
|
||||
def remove_if_exists(self, target):
|
||||
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
try:
|
||||
objects = ldap.search(target + ",dc=yunohost,dc=org")
|
||||
# ldap search will raise an exception if no corresponding object is found >.> ...
|
||||
except Exception as e:
|
||||
logger.debug("%s does not exist, no need to delete it" % target)
|
||||
return
|
||||
|
||||
objects.reverse()
|
||||
for o in objects:
|
||||
for dn in o["dn"]:
|
||||
dn = dn.replace(",dc=yunohost,dc=org", "")
|
||||
logger.debug("Deleting old object %s ..." % dn)
|
||||
try:
|
||||
ldap.remove(dn)
|
||||
except Exception as e:
|
||||
raise YunohostError("migration_0011_failed_to_remove_stale_object", dn=dn, error=e)
|
||||
|
||||
def migrate_LDAP_db(self):
|
||||
|
||||
logger.info(m18n.n("migration_0011_update_LDAP_database"))
|
||||
|
@ -35,21 +57,24 @@ class MyMigration(Migration):
|
|||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
try:
|
||||
ldap.remove('cn=sftpusers,ou=groups')
|
||||
except:
|
||||
logger.warn(m18n.n("error_when_removing_sftpuser_group"))
|
||||
|
||||
with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f:
|
||||
ldap_map = yaml.load(f)
|
||||
ldap_map = read_yaml('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml')
|
||||
|
||||
try:
|
||||
self.remove_if_exists("ou=permission")
|
||||
self.remove_if_exists('ou=groups')
|
||||
|
||||
attr_dict = ldap_map['parents']['ou=permission']
|
||||
ldap.add('ou=permission', attr_dict)
|
||||
|
||||
attr_dict = ldap_map['parents']['ou=groups']
|
||||
ldap.add('ou=groups', attr_dict)
|
||||
|
||||
attr_dict = ldap_map['children']['cn=all_users,ou=groups']
|
||||
ldap.add('cn=all_users,ou=groups', attr_dict)
|
||||
|
||||
attr_dict = ldap_map['children']['cn=visitors,ou=groups']
|
||||
ldap.add('cn=visitors,ou=groups', attr_dict)
|
||||
|
||||
for rdn, attr_dict in ldap_map['depends_children'].items():
|
||||
ldap.add(rdn, attr_dict)
|
||||
except Exception as e:
|
||||
|
@ -65,10 +90,8 @@ class MyMigration(Migration):
|
|||
username = user_info['uid'][0]
|
||||
ldap.update('uid=%s,ou=users' % username,
|
||||
{'objectClass': ['mailAccount', 'inetOrgPerson', 'posixAccount', 'userPermissionYnh']})
|
||||
user_group_add(username, gid=user_info['uidNumber'][0], sync_perm=False)
|
||||
user_group_update(groupname=username, add_user=username, force=True, sync_perm=False)
|
||||
user_group_update(groupname='all_users', add_user=username, force=True, sync_perm=False)
|
||||
|
||||
user_group_create(username, gid=user_info['uidNumber'][0], primary_group=True, sync_perm=False)
|
||||
user_group_update(groupname='all_users', add=username, force=True, sync_perm=False)
|
||||
|
||||
def migrate_app_permission(self, app=None):
|
||||
logger.info(m18n.n("migration_0011_migrate_permission"))
|
||||
|
@ -84,20 +107,33 @@ class MyMigration(Migration):
|
|||
path = app_setting(app, 'path')
|
||||
domain = app_setting(app, 'domain')
|
||||
|
||||
urls = [domain + path] if domain and path else None
|
||||
permission_add(app, permission='main', urls=urls, default_allow=True, sync_perm=False)
|
||||
url = "/" if domain and path else None
|
||||
if permission:
|
||||
allowed_group = permission.split(',')
|
||||
user_permission_add([app], permission='main', group=allowed_group, sync_perm=False)
|
||||
known_users = user_list()["users"].keys()
|
||||
allowed = [user for user in permission.split(',') if user in known_users]
|
||||
else:
|
||||
allowed = ["all_users"]
|
||||
permission_create(app+".main", url=url, allowed=allowed, sync_perm=False)
|
||||
|
||||
app_setting(app, 'allowed_users', delete=True)
|
||||
|
||||
# Migrate classic public app still using the legacy unprotected_uris
|
||||
if app_setting(app, "unprotected_uris") == "/" or app_setting(app, "skipped_uris") == "/":
|
||||
user_permission_update(app+".main", remove="all_users", add="visitors", sync_perm=False)
|
||||
|
||||
permission_sync_to_user()
|
||||
|
||||
def run(self):
|
||||
|
||||
# FIXME : what do we really want to do here ...
|
||||
# Imho we should just force-regen the conf in all case, and maybe
|
||||
# just display a warning if we detect that the conf was manually modified
|
||||
|
||||
def migrate(self):
|
||||
# Check if the migration can be processed
|
||||
ldap_regen_conf_status = regen_conf(names=['slapd'], dry_run=True)
|
||||
# By this we check if the have been customized
|
||||
if ldap_regen_conf_status and ldap_regen_conf_status['slapd']['pending']:
|
||||
raise YunohostError("migration_0011_LDAP_config_dirty")
|
||||
logger.warning(m18n.n("migration_0011_slapd_config_will_be_overwritten", conf_backup_folder=BACKUP_CONF_DIR))
|
||||
|
||||
# Backup LDAP and the apps settings before to do the migration
|
||||
logger.info(m18n.n("migration_0011_backup_before_migration"))
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import glob
|
||||
import re
|
||||
from yunohost.tools import Migration
|
||||
from moulinette.utils.filesystem import read_file, write_to_file
|
||||
|
||||
|
||||
class MyMigration(Migration):
|
||||
|
||||
"Force authentication in md5 for local connexions"
|
||||
|
||||
all_hba_files = glob.glob("/etc/postgresql/*/*/pg_hba.conf")
|
||||
|
||||
def run(self):
|
||||
for filename in self.all_hba_files:
|
||||
pg_hba_in = read_file(filename)
|
||||
write_to_file(filename, re.sub(r"local(\s*)all(\s*)all(\s*)password", "local\\1all\\2all\\3md5", pg_hba_in))
|
426
src/yunohost/diagnosis.py
Normal file
426
src/yunohost/diagnosis.py
Normal file
|
@ -0,0 +1,426 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" License
|
||||
|
||||
Copyright (C) 2018 YunoHost
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
|
||||
""" diagnosis.py
|
||||
|
||||
Look for possible issues on the server
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from moulinette import m18n, msettings
|
||||
from moulinette.utils import log
|
||||
from moulinette.utils.filesystem import read_json, write_to_json, read_yaml, write_to_yaml
|
||||
|
||||
from yunohost.utils.error import YunohostError
|
||||
from yunohost.hook import hook_list, hook_exec
|
||||
|
||||
logger = log.getActionLogger('yunohost.diagnosis')
|
||||
|
||||
DIAGNOSIS_CACHE = "/var/cache/yunohost/diagnosis/"
|
||||
DIAGNOSIS_CONFIG_FILE = '/etc/yunohost/diagnosis.yml'
|
||||
|
||||
def diagnosis_list():
|
||||
all_categories_names = [h for h, _ in _list_diagnosis_categories()]
|
||||
return {"categories": all_categories_names}
|
||||
|
||||
|
||||
def diagnosis_show(categories=[], issues=False, full=False, share=False):
|
||||
|
||||
# Get all the categories
|
||||
all_categories = _list_diagnosis_categories()
|
||||
all_categories_names = [category for category, _ in all_categories]
|
||||
|
||||
# Check the requested category makes sense
|
||||
if categories == []:
|
||||
categories = all_categories_names
|
||||
else:
|
||||
unknown_categories = [c for c in categories if c not in all_categories_names]
|
||||
if unknown_categories:
|
||||
raise YunohostError('diagnosis_unknown_categories', categories=", ".join(categories))
|
||||
|
||||
# Fetch all reports
|
||||
all_reports = []
|
||||
for category in categories:
|
||||
try:
|
||||
report = Diagnoser.get_cached_report(category)
|
||||
except Exception as e:
|
||||
logger.error(m18n.n("diagnosis_failed", category=category, error=str(e)))
|
||||
else:
|
||||
add_ignore_flag_to_issues(report)
|
||||
if not full:
|
||||
del report["timestamp"]
|
||||
del report["cached_for"]
|
||||
report["items"] = [item for item in report["items"] if not item["ignored"]]
|
||||
for item in report["items"]:
|
||||
del item["meta"]
|
||||
del item["ignored"]
|
||||
if "data" in item:
|
||||
del item["data"]
|
||||
if issues:
|
||||
report["items"] = [item for item in report["items"] if item["status"] in ["WARNING", "ERROR"]]
|
||||
# Ignore this category if no issue was found
|
||||
if not report["items"]:
|
||||
continue
|
||||
|
||||
all_reports.append(report)
|
||||
|
||||
if share:
|
||||
from yunohost.utils.yunopaste import yunopaste
|
||||
content = _dump_human_readable_reports(all_reports)
|
||||
url = yunopaste(content)
|
||||
|
||||
logger.info(m18n.n("log_available_on_yunopaste", url=url))
|
||||
if msettings.get('interface') == 'api':
|
||||
return {"url": url}
|
||||
else:
|
||||
return
|
||||
else:
|
||||
return {"reports": all_reports}
|
||||
|
||||
def _dump_human_readable_reports(reports):
|
||||
|
||||
output = ""
|
||||
|
||||
for report in reports:
|
||||
output += "=================================\n"
|
||||
output += "{description} ({id})\n".format(**report)
|
||||
output += "=================================\n\n"
|
||||
for item in report["items"]:
|
||||
output += "[{status}] {summary}\n".format(**item)
|
||||
for detail in item.get("details", []):
|
||||
output += " - " + detail + "\n"
|
||||
output += "\n"
|
||||
output += "\n\n"
|
||||
|
||||
return(output)
|
||||
|
||||
|
||||
def diagnosis_run(categories=[], force=False):
|
||||
|
||||
# Get all the categories
|
||||
all_categories = _list_diagnosis_categories()
|
||||
all_categories_names = [category for category, _ in all_categories]
|
||||
|
||||
# Check the requested category makes sense
|
||||
if categories == []:
|
||||
categories = all_categories_names
|
||||
else:
|
||||
unknown_categories = [c for c in categories if c not in all_categories_names]
|
||||
if unknown_categories:
|
||||
raise YunohostError('diagnosis_unknown_categories', categories=", ".join(unknown_categories))
|
||||
|
||||
issues = []
|
||||
# Call the hook ...
|
||||
diagnosed_categories = []
|
||||
for category in categories:
|
||||
logger.debug("Running diagnosis for %s ..." % category)
|
||||
path = [p for n, p in all_categories if n == category][0]
|
||||
|
||||
try:
|
||||
code, report = hook_exec(path, args={"force": force}, env=None)
|
||||
except Exception as e:
|
||||
logger.error(m18n.n("diagnosis_failed_for_category", category=category, error=str(e)), exc_info=True)
|
||||
else:
|
||||
diagnosed_categories.append(category)
|
||||
if report != {}:
|
||||
issues.extend([item for item in report["items"] if item["status"] in ["WARNING", "ERROR"]])
|
||||
|
||||
if issues:
|
||||
if msettings.get("interface") == "api":
|
||||
logger.info(m18n.n("diagnosis_display_tip_web"))
|
||||
else:
|
||||
logger.info(m18n.n("diagnosis_display_tip_cli"))
|
||||
|
||||
return
|
||||
|
||||
|
||||
def diagnosis_ignore(add_filter=None, remove_filter=None, list=False):
|
||||
"""
|
||||
This action is meant for the admin to ignore issues reported by the
|
||||
diagnosis system if they are known and understood by the admin. For
|
||||
example, the lack of ipv6 on an instance, or badly configured XMPP dns
|
||||
records if the admin doesn't care so much about XMPP. The point being that
|
||||
the diagnosis shouldn't keep complaining about those known and "expected"
|
||||
issues, and instead focus on new unexpected issues that could arise.
|
||||
|
||||
For example, to ignore badly XMPP dnsrecords for domain yolo.test:
|
||||
|
||||
yunohost diagnosis ignore --add-filter dnsrecords domain=yolo.test category=xmpp
|
||||
^ ^ ^
|
||||
the general additional other
|
||||
diagnosis criterias criteria
|
||||
category to to target to target
|
||||
act on specific specific
|
||||
reports reports
|
||||
Or to ignore all dnsrecords issues:
|
||||
|
||||
yunohost diagnosis ignore --add-filter dnsrecords
|
||||
|
||||
The filters are stored in the diagnosis configuration in a data structure like:
|
||||
|
||||
ignore_filters: {
|
||||
"ip": [
|
||||
{"version": 6} # Ignore all issues related to ipv6
|
||||
],
|
||||
"dnsrecords": [
|
||||
{"domain": "yolo.test", "category": "xmpp"}, # Ignore all issues related to DNS xmpp records for yolo.test
|
||||
{} # Ignore all issues about dnsrecords
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
# Ignore filters are stored in
|
||||
configuration = _diagnosis_read_configuration()
|
||||
|
||||
if list:
|
||||
return {"ignore_filters": configuration.get("ignore_filters", {})}
|
||||
|
||||
def validate_filter_criterias(filter_):
|
||||
|
||||
# Get all the categories
|
||||
all_categories = _list_diagnosis_categories()
|
||||
all_categories_names = [category for category, _ in all_categories]
|
||||
|
||||
# Sanity checks for the provided arguments
|
||||
if len(filter_) == 0:
|
||||
raise YunohostError("You should provide at least one criteria being the diagnosis category to ignore")
|
||||
category = filter_[0]
|
||||
if category not in all_categories_names:
|
||||
raise YunohostError("%s is not a diagnosis category" % category)
|
||||
if any("=" not in criteria for criteria in filter_[1:]):
|
||||
raise YunohostError("Extra criterias should be of the form key=value (e.g. domain=yolo.test)")
|
||||
|
||||
# Convert the provided criteria into a nice dict
|
||||
criterias = {c.split("=")[0]: c.split("=")[1] for c in filter_[1:]}
|
||||
|
||||
return category, criterias
|
||||
|
||||
if add_filter:
|
||||
|
||||
category, criterias = validate_filter_criterias(add_filter)
|
||||
|
||||
# Fetch current issues for the requested category
|
||||
current_issues_for_this_category = diagnosis_show(categories=[category], issues=True, full=True)
|
||||
current_issues_for_this_category = current_issues_for_this_category["reports"][0].get("items", {})
|
||||
|
||||
# Accept the given filter only if the criteria effectively match an existing issue
|
||||
if not any(issue_matches_criterias(i, criterias) for i in current_issues_for_this_category):
|
||||
raise YunohostError("No issues was found matching the given criteria.")
|
||||
|
||||
# Make sure the subdicts/lists exists
|
||||
if "ignore_filters" not in configuration:
|
||||
configuration["ignore_filters"] = {}
|
||||
if category not in configuration["ignore_filters"]:
|
||||
configuration["ignore_filters"][category] = []
|
||||
|
||||
if criterias in configuration["ignore_filters"][category]:
|
||||
logger.warning("This filter already exists.")
|
||||
return
|
||||
|
||||
configuration["ignore_filters"][category].append(criterias)
|
||||
_diagnosis_write_configuration(configuration)
|
||||
logger.success("Filter added")
|
||||
return
|
||||
|
||||
if remove_filter:
|
||||
|
||||
category, criterias = validate_filter_criterias(remove_filter)
|
||||
|
||||
# Make sure the subdicts/lists exists
|
||||
if "ignore_filters" not in configuration:
|
||||
configuration["ignore_filters"] = {}
|
||||
if category not in configuration["ignore_filters"]:
|
||||
configuration["ignore_filters"][category] = []
|
||||
|
||||
if criterias not in configuration["ignore_filters"][category]:
|
||||
raise YunohostError("This filter does not exists.")
|
||||
|
||||
configuration["ignore_filters"][category].remove(criterias)
|
||||
_diagnosis_write_configuration(configuration)
|
||||
logger.success("Filter removed")
|
||||
return
|
||||
|
||||
|
||||
def _diagnosis_read_configuration():
|
||||
if not os.path.exists(DIAGNOSIS_CONFIG_FILE):
|
||||
return {}
|
||||
|
||||
return read_yaml(DIAGNOSIS_CONFIG_FILE)
|
||||
|
||||
|
||||
def _diagnosis_write_configuration(conf):
|
||||
write_to_yaml(DIAGNOSIS_CONFIG_FILE, conf)
|
||||
|
||||
|
||||
def issue_matches_criterias(issue, criterias):
|
||||
"""
|
||||
e.g. an issue with:
|
||||
meta:
|
||||
domain: yolo.test
|
||||
category: xmpp
|
||||
|
||||
matches the criterias {"domain": "yolo.test"}
|
||||
"""
|
||||
for key, value in criterias.items():
|
||||
if key not in issue["meta"]:
|
||||
return False
|
||||
if str(issue["meta"][key]) != value:
|
||||
return False
|
||||
return True
|
||||
|
||||
def add_ignore_flag_to_issues(report):
|
||||
"""
|
||||
Iterate over issues in a report, and flag them as ignored if they match an
|
||||
ignored filter from the configuration
|
||||
|
||||
N.B. : for convenience. we want to make sure the "ignored" key is set for
|
||||
every item in the report
|
||||
"""
|
||||
|
||||
ignore_filters = _diagnosis_read_configuration().get("ignore_filters", {}).get(report["id"], [])
|
||||
|
||||
for report_item in report["items"]:
|
||||
report_item["ignored"] = False
|
||||
if report_item["status"] not in ["WARNING", "ERROR"]:
|
||||
continue
|
||||
for criterias in ignore_filters:
|
||||
if issue_matches_criterias(report_item, criterias):
|
||||
report_item["ignored"] = True
|
||||
break
|
||||
|
||||
|
||||
############################################################
|
||||
|
||||
|
||||
class Diagnoser():
|
||||
|
||||
def __init__(self, args, env, loggers):
|
||||
|
||||
# FIXME ? That stuff with custom loggers is weird ... (mainly inherited from the bash hooks, idk)
|
||||
self.logger_debug, self.logger_warning, self.logger_info = loggers
|
||||
self.env = env
|
||||
self.args = args or {}
|
||||
self.cache_file = Diagnoser.cache_file(self.id_)
|
||||
self.description = Diagnoser.get_description(self.id_)
|
||||
|
||||
def cached_time_ago(self):
|
||||
|
||||
if not os.path.exists(self.cache_file):
|
||||
return 99999999
|
||||
return time.time() - os.path.getmtime(self.cache_file)
|
||||
|
||||
def write_cache(self, report):
|
||||
if not os.path.exists(DIAGNOSIS_CACHE):
|
||||
os.makedirs(DIAGNOSIS_CACHE)
|
||||
return write_to_json(self.cache_file, report)
|
||||
|
||||
def diagnose(self):
|
||||
|
||||
if not self.args.get("force", False) and self.cached_time_ago() < self.cache_duration:
|
||||
self.logger_debug("Cache still valid : %s" % self.cache_file)
|
||||
logger.info(m18n.n("diagnosis_cache_still_valid", category=self.description))
|
||||
return 0, {}
|
||||
|
||||
for dependency in self.dependencies:
|
||||
dep_report = Diagnoser.get_cached_report(dependency)
|
||||
dep_errors = [item for item in dep_report["items"] if item["status"] == "ERROR"]
|
||||
if dep_errors:
|
||||
logger.error(m18n.n("diagnosis_cant_run_because_of_dep", category=self.description, dep=Diagnoser.get_description(dependency)))
|
||||
return 1, {}
|
||||
|
||||
self.logger_debug("Running diagnostic for %s" % self.id_)
|
||||
|
||||
items = list(self.run())
|
||||
|
||||
new_report = {"id": self.id_,
|
||||
"cached_for": self.cache_duration,
|
||||
"items": items}
|
||||
|
||||
self.logger_debug("Updating cache %s" % self.cache_file)
|
||||
self.write_cache(new_report)
|
||||
Diagnoser.i18n(new_report)
|
||||
add_ignore_flag_to_issues(new_report)
|
||||
|
||||
errors = [item for item in new_report["items"] if item["status"] == "ERROR" and not item["ignored"]]
|
||||
warnings = [item for item in new_report["items"] if item["status"] == "WARNING" and not item["ignored"]]
|
||||
errors_ignored = [item for item in new_report["items"] if item["status"] == "ERROR" and item["ignored"]]
|
||||
warning_ignored = [item for item in new_report["items"] if item["status"] == "WARNING" and item["ignored"]]
|
||||
ignored_msg = " " + m18n.n("diagnosis_ignored_issues", nb_ignored=len(errors_ignored+warning_ignored)) if errors_ignored or warning_ignored else ""
|
||||
|
||||
if errors and warnings:
|
||||
logger.error(m18n.n("diagnosis_found_errors_and_warnings", errors=len(errors), warnings=len(warnings), category=new_report["description"]) + ignored_msg)
|
||||
elif errors:
|
||||
logger.error(m18n.n("diagnosis_found_errors", errors=len(errors), category=new_report["description"]) + ignored_msg)
|
||||
elif warnings:
|
||||
logger.warning(m18n.n("diagnosis_found_warnings", warnings=len(warnings), category=new_report["description"]) + ignored_msg)
|
||||
else:
|
||||
logger.success(m18n.n("diagnosis_everything_ok", category=new_report["description"]) + ignored_msg)
|
||||
|
||||
return 0, new_report
|
||||
|
||||
@staticmethod
|
||||
def cache_file(id_):
|
||||
return os.path.join(DIAGNOSIS_CACHE, "%s.json" % id_)
|
||||
|
||||
@staticmethod
|
||||
def get_cached_report(id_):
|
||||
filename = Diagnoser.cache_file(id_)
|
||||
report = read_json(filename)
|
||||
report["timestamp"] = int(os.path.getmtime(filename))
|
||||
Diagnoser.i18n(report)
|
||||
return report
|
||||
|
||||
@staticmethod
|
||||
def get_description(id_):
|
||||
key = "diagnosis_description_" + id_
|
||||
descr = m18n.n(key)
|
||||
# If no description available, fallback to id
|
||||
return descr if descr != key else id_
|
||||
|
||||
@staticmethod
|
||||
def i18n(report):
|
||||
|
||||
# "Render" the strings with m18n.n
|
||||
# N.B. : we do those m18n.n right now instead of saving the already-translated report
|
||||
# because we can't be sure we'll redisplay the infos with the same locale as it
|
||||
# was generated ... e.g. if the diagnosing happened inside a cron job with locale EN
|
||||
# instead of FR used by the actual admin...
|
||||
|
||||
report["description"] = Diagnoser.get_description(report["id"])
|
||||
|
||||
for item in report["items"]:
|
||||
summary_key, summary_args = item["summary"]
|
||||
item["summary"] = m18n.n(summary_key, **summary_args)
|
||||
|
||||
if "details" in item:
|
||||
item["details"] = [m18n.n(key, *values) for key, values in item["details"]]
|
||||
|
||||
|
||||
def _list_diagnosis_categories():
|
||||
hooks_raw = hook_list("diagnosis", list_by="priority", show_info=True)["hooks"]
|
||||
hooks = []
|
||||
for _, some_hooks in sorted(hooks_raw.items(), key=lambda h: int(h[0])):
|
||||
for name, info in some_hooks.items():
|
||||
hooks.append((name, info["path"]))
|
||||
|
||||
return hooks
|
|
@ -34,6 +34,7 @@ from moulinette.utils.log import getActionLogger
|
|||
|
||||
import yunohost.certificate
|
||||
|
||||
from yunohost.app import app_ssowatconf
|
||||
from yunohost.regenconf import regen_conf
|
||||
from yunohost.utils.network import get_public_ip
|
||||
from yunohost.log import is_unit_operation
|
||||
|
@ -112,8 +113,10 @@ def domain_add(operation_logger, domain, dyndns=False):
|
|||
'virtualdomain': domain,
|
||||
}
|
||||
|
||||
if not ldap.add('virtualdomain=%s,ou=domains' % domain, attr_dict):
|
||||
raise YunohostError('domain_creation_failed')
|
||||
try:
|
||||
ldap.add('virtualdomain=%s,ou=domains' % domain, attr_dict)
|
||||
except Exception as e:
|
||||
raise YunohostError('domain_creation_failed', domain=domain, error=e)
|
||||
|
||||
# Don't regen these conf if we're still in postinstall
|
||||
if os.path.exists('/etc/yunohost/installed'):
|
||||
|
@ -152,7 +155,14 @@ def domain_remove(operation_logger, domain, force=False):
|
|||
|
||||
# Check domain is not the main domain
|
||||
if domain == _get_maindomain():
|
||||
raise YunohostError('domain_cannot_remove_main')
|
||||
other_domains = domain_list()["domains"]
|
||||
other_domains.remove(domain)
|
||||
|
||||
if other_domains:
|
||||
raise YunohostError('domain_cannot_remove_main',
|
||||
domain=domain, other_domains="\n * " + ("\n * ".join(other_domains)))
|
||||
else:
|
||||
raise YunohostError('domain_cannot_remove_main_add_new_one', domain=domain)
|
||||
|
||||
# Check if apps are installed on the domain
|
||||
for app in os.listdir('/etc/yunohost/apps/'):
|
||||
|
@ -167,10 +177,12 @@ def domain_remove(operation_logger, domain, force=False):
|
|||
|
||||
operation_logger.start()
|
||||
ldap = _get_ldap_interface()
|
||||
if ldap.remove('virtualdomain=' + domain + ',ou=domains') or force:
|
||||
os.system('rm -rf /etc/yunohost/certs/%s' % domain)
|
||||
else:
|
||||
raise YunohostError('domain_deletion_failed')
|
||||
try:
|
||||
ldap.remove('virtualdomain=' + domain + ',ou=domains')
|
||||
except Exception as e:
|
||||
raise YunohostError('domain_deletion_failed', domain=domain, error=e)
|
||||
|
||||
os.system('rm -rf /etc/yunohost/certs/%s' % domain)
|
||||
|
||||
regen_conf(names=['nginx', 'metronome', 'dnsmasq', 'postfix'])
|
||||
app_ssowatconf()
|
||||
|
@ -229,6 +241,63 @@ def domain_dns_conf(domain, ttl=None):
|
|||
return result
|
||||
|
||||
|
||||
@is_unit_operation()
|
||||
def domain_main_domain(operation_logger, new_main_domain=None):
|
||||
"""
|
||||
Check the current main domain, or change it
|
||||
|
||||
Keyword argument:
|
||||
new_main_domain -- The new domain to be set as the main domain
|
||||
|
||||
"""
|
||||
from yunohost.tools import _set_hostname
|
||||
|
||||
# If no new domain specified, we return the current main domain
|
||||
if not new_main_domain:
|
||||
return {'current_main_domain': _get_maindomain()}
|
||||
|
||||
# Check domain exists
|
||||
if new_main_domain not in domain_list()['domains']:
|
||||
raise YunohostError('domain_unknown')
|
||||
|
||||
operation_logger.related_to.append(('domain', new_main_domain))
|
||||
operation_logger.start()
|
||||
|
||||
# Apply changes to ssl certs
|
||||
ssl_key = "/etc/ssl/private/yunohost_key.pem"
|
||||
ssl_crt = "/etc/ssl/private/yunohost_crt.pem"
|
||||
new_ssl_key = "/etc/yunohost/certs/%s/key.pem" % new_main_domain
|
||||
new_ssl_crt = "/etc/yunohost/certs/%s/crt.pem" % new_main_domain
|
||||
|
||||
try:
|
||||
if os.path.exists(ssl_key) or os.path.lexists(ssl_key):
|
||||
os.remove(ssl_key)
|
||||
if os.path.exists(ssl_crt) or os.path.lexists(ssl_crt):
|
||||
os.remove(ssl_crt)
|
||||
|
||||
os.symlink(new_ssl_key, ssl_key)
|
||||
os.symlink(new_ssl_crt, ssl_crt)
|
||||
|
||||
_set_maindomain(new_main_domain)
|
||||
except Exception as e:
|
||||
logger.warning("%s" % e, exc_info=1)
|
||||
raise YunohostError('main_domain_change_failed')
|
||||
|
||||
_set_hostname(new_main_domain)
|
||||
|
||||
# Generate SSOwat configuration file
|
||||
app_ssowatconf()
|
||||
|
||||
# Regen configurations
|
||||
try:
|
||||
with open('/etc/yunohost/installed', 'r'):
|
||||
regen_conf()
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
logger.success(m18n.n('main_domain_changed'))
|
||||
|
||||
|
||||
def domain_cert_status(domain_list, full=False):
|
||||
return yunohost.certificate.certificate_status(domain_list, full)
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ import subprocess
|
|||
from moulinette import m18n
|
||||
from moulinette.core import MoulinetteError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.filesystem import write_to_file
|
||||
from moulinette.utils.filesystem import write_to_file, read_file
|
||||
from moulinette.utils.network import download_json
|
||||
from moulinette.utils.process import check_output
|
||||
|
||||
|
@ -176,7 +176,7 @@ def dyndns_subscribe(operation_logger, subscribe_host="dyndns.yunohost.org", dom
|
|||
|
||||
@is_unit_operation()
|
||||
def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None, key=None,
|
||||
ipv4=None, ipv6=None):
|
||||
ipv4=None, ipv6=None, force=False, dry_run=False):
|
||||
"""
|
||||
Update IP on DynDNS platform
|
||||
|
||||
|
@ -212,7 +212,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
|
|||
from yunohost.tools import _get_migration_by_name
|
||||
migration = _get_migration_by_name("migrate_to_tsig_sha256")
|
||||
try:
|
||||
migration.migrate(dyn_host, domain, key)
|
||||
migration.run(dyn_host, domain, key)
|
||||
except Exception as e:
|
||||
logger.error(m18n.n('migrations_migration_has_failed',
|
||||
exception=e,
|
||||
|
@ -249,7 +249,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
|
|||
logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6))
|
||||
|
||||
# no need to update
|
||||
if old_ipv4 == ipv4 and old_ipv6 == ipv6:
|
||||
if (not force and not dry_run) and (old_ipv4 == ipv4 and old_ipv6 == ipv6):
|
||||
logger.info("No updated needed.")
|
||||
return
|
||||
else:
|
||||
|
@ -279,7 +279,7 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
|
|||
# should be muc.the.domain.tld. or the.domain.tld
|
||||
if record["value"] == "@":
|
||||
record["value"] = domain
|
||||
record["value"] = record["value"].replace(";", "\;")
|
||||
record["value"] = record["value"].replace(";", r"\;")
|
||||
|
||||
action = "update add {name}.{domain}. {ttl} {type} {value}".format(domain=domain, **record)
|
||||
action = action.replace(" @.", " ")
|
||||
|
@ -296,13 +296,18 @@ def dyndns_update(operation_logger, dyn_host="dyndns.yunohost.org", domain=None,
|
|||
|
||||
logger.debug("Now pushing new conf to DynDNS host...")
|
||||
|
||||
try:
|
||||
command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE]
|
||||
subprocess.check_call(command)
|
||||
except subprocess.CalledProcessError:
|
||||
raise YunohostError('dyndns_ip_update_failed')
|
||||
if not dry_run:
|
||||
try:
|
||||
command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE]
|
||||
subprocess.check_call(command)
|
||||
except subprocess.CalledProcessError:
|
||||
raise YunohostError('dyndns_ip_update_failed')
|
||||
|
||||
logger.success(m18n.n('dyndns_ip_updated'))
|
||||
logger.success(m18n.n('dyndns_ip_updated'))
|
||||
else:
|
||||
print(read_file(DYNDNS_ZONE))
|
||||
print("")
|
||||
print("Warning: dry run, this is only the generated config, it won't be applied")
|
||||
|
||||
|
||||
def dyndns_installcron():
|
||||
|
@ -325,8 +330,8 @@ def dyndns_removecron():
|
|||
"""
|
||||
try:
|
||||
os.remove("/etc/cron.d/yunohost-dyndns")
|
||||
except:
|
||||
raise YunohostError('dyndns_cron_remove_failed')
|
||||
except Exception as e:
|
||||
raise YunohostError('dyndns_cron_remove_failed', error=e)
|
||||
|
||||
logger.success(m18n.n('dyndns_cron_removed'))
|
||||
|
||||
|
|
|
@ -25,8 +25,11 @@
|
|||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import tempfile
|
||||
import mimetypes
|
||||
from glob import iglob
|
||||
from importlib import import_module
|
||||
|
||||
from moulinette import m18n, msettings
|
||||
from yunohost.utils.error import YunohostError
|
||||
|
@ -179,7 +182,7 @@ def hook_list(action, list_by='name', show_info=False):
|
|||
def _append_folder(d, folder):
|
||||
# Iterate over and add hook from a folder
|
||||
for f in os.listdir(folder + action):
|
||||
if f[0] == '.' or f[-1] == '~':
|
||||
if f[0] == '.' or f[-1] == '~' or f.endswith(".pyc"):
|
||||
continue
|
||||
path = '%s%s/%s' % (folder, action, f)
|
||||
priority, name = _extract_filename_parts(f)
|
||||
|
@ -193,7 +196,7 @@ def hook_list(action, list_by='name', show_info=False):
|
|||
else:
|
||||
_append_folder(result, HOOK_FOLDER)
|
||||
except OSError:
|
||||
logger.debug("system hook folder not found for action '%s' in %s",
|
||||
logger.debug("No default hook for action '%s' in %s",
|
||||
action, HOOK_FOLDER)
|
||||
|
||||
try:
|
||||
|
@ -204,7 +207,7 @@ def hook_list(action, list_by='name', show_info=False):
|
|||
else:
|
||||
_append_folder(result, CUSTOM_HOOK_FOLDER)
|
||||
except OSError:
|
||||
logger.debug("custom hook folder not found for action '%s' in %s",
|
||||
logger.debug("No custom hook for action '%s' in %s",
|
||||
action, CUSTOM_HOOK_FOLDER)
|
||||
|
||||
return {'hooks': result}
|
||||
|
@ -311,7 +314,6 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
|
|||
user -- User with which to run the command
|
||||
|
||||
"""
|
||||
from moulinette.utils.process import call_async_output
|
||||
|
||||
# Validate hook path
|
||||
if path[0] != '/':
|
||||
|
@ -319,6 +321,38 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
|
|||
if not os.path.isfile(path):
|
||||
raise YunohostError('file_does_not_exist', path=path)
|
||||
|
||||
# Define output loggers and call command
|
||||
loggers = (
|
||||
lambda l: logger.debug(l.rstrip()+"\r"),
|
||||
lambda l: logger.warning(l.rstrip()),
|
||||
lambda l: logger.info(l.rstrip())
|
||||
)
|
||||
|
||||
# Check the type of the hook (bash by default)
|
||||
# For now we support only python and bash hooks.
|
||||
hook_type = mimetypes.MimeTypes().guess_type(path)[0]
|
||||
if hook_type == 'text/x-python':
|
||||
returncode, returndata = _hook_exec_python(path, args, env, loggers)
|
||||
else:
|
||||
returncode, returndata = _hook_exec_bash(path, args, no_trace, chdir, env, user, return_format, loggers)
|
||||
|
||||
# Check and return process' return code
|
||||
if returncode is None:
|
||||
if raise_on_error:
|
||||
raise YunohostError('hook_exec_not_terminated', path=path)
|
||||
else:
|
||||
logger.error(m18n.n('hook_exec_not_terminated', path=path))
|
||||
return 1, {}
|
||||
elif raise_on_error and returncode != 0:
|
||||
raise YunohostError('hook_exec_failed', path=path)
|
||||
|
||||
return returncode, returndata
|
||||
|
||||
|
||||
def _hook_exec_bash(path, args, no_trace, chdir, env, user, return_format, loggers):
|
||||
|
||||
from moulinette.utils.process import call_async_output
|
||||
|
||||
# Construct command variables
|
||||
cmd_args = ''
|
||||
if args and isinstance(args, list):
|
||||
|
@ -369,33 +403,13 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
|
|||
else:
|
||||
logger.debug(m18n.n('executing_script', script=path))
|
||||
|
||||
# Define output callbacks and call command
|
||||
callbacks = (
|
||||
lambda l: logger.debug(l.rstrip()+"\r"),
|
||||
lambda l: logger.warning(l.rstrip()),
|
||||
)
|
||||
|
||||
if stdinfo:
|
||||
callbacks = (callbacks[0], callbacks[1],
|
||||
lambda l: logger.info(l.rstrip()))
|
||||
|
||||
logger.debug("About to run the command '%s'" % command)
|
||||
|
||||
returncode = call_async_output(
|
||||
command, callbacks, shell=False, cwd=chdir,
|
||||
command, loggers, shell=False, cwd=chdir,
|
||||
stdinfo=stdinfo
|
||||
)
|
||||
|
||||
# Check and return process' return code
|
||||
if returncode is None:
|
||||
if raise_on_error:
|
||||
raise YunohostError('hook_exec_not_terminated', path=path)
|
||||
else:
|
||||
logger.error(m18n.n('hook_exec_not_terminated', path=path))
|
||||
return 1, {}
|
||||
elif raise_on_error and returncode != 0:
|
||||
raise YunohostError('hook_exec_failed', path=path)
|
||||
|
||||
raw_content = None
|
||||
try:
|
||||
with open(stdreturn, 'r') as f:
|
||||
|
@ -427,6 +441,25 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
|
|||
return returncode, returncontent
|
||||
|
||||
|
||||
def _hook_exec_python(path, args, env, loggers):
|
||||
|
||||
dir_ = os.path.dirname(path)
|
||||
name = os.path.splitext(os.path.basename(path))[0]
|
||||
|
||||
if not dir_ in sys.path:
|
||||
sys.path = [dir_] + sys.path
|
||||
module = import_module(name)
|
||||
|
||||
ret = module.main(args, env, loggers)
|
||||
# # Assert that the return is a (int, dict) tuple
|
||||
assert isinstance(ret, tuple) \
|
||||
and len(ret) == 2 \
|
||||
and isinstance(ret[0],int) \
|
||||
and isinstance(ret[1],dict), \
|
||||
"Module %s did not return a (int, dict) tuple !" % module
|
||||
return ret
|
||||
|
||||
|
||||
def _extract_filename_parts(filename):
|
||||
"""Extract hook parts from filename"""
|
||||
if '-' in filename:
|
||||
|
@ -434,6 +467,9 @@ def _extract_filename_parts(filename):
|
|||
else:
|
||||
priority = '50'
|
||||
action = filename
|
||||
|
||||
# Remove extension if there's one
|
||||
action = os.path.splitext(action)[0]
|
||||
return priority, action
|
||||
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ CATEGORIES = ['operation', 'history', 'package', 'system', 'access', 'service',
|
|||
'app']
|
||||
METADATA_FILE_EXT = '.yml'
|
||||
LOG_FILE_EXT = '.log'
|
||||
RELATED_CATEGORIES = ['app', 'domain', 'service', 'user']
|
||||
RELATED_CATEGORIES = ['app', 'domain', 'group', 'service', 'user']
|
||||
|
||||
logger = getActionLogger('yunohost.log')
|
||||
|
||||
|
@ -213,7 +213,7 @@ def log_display(path, number=None, share=False):
|
|||
return infos
|
||||
|
||||
|
||||
def is_unit_operation(entities=['app', 'domain', 'service', 'user'],
|
||||
def is_unit_operation(entities=['app', 'domain', 'group', 'service', 'user'],
|
||||
exclude=['password'], operation_key=None):
|
||||
"""
|
||||
Configure quickly a unit operation
|
||||
|
@ -315,7 +315,8 @@ class RedactingFormatter(Formatter):
|
|||
try:
|
||||
# This matches stuff like db_pwd=the_secret or admin_password=other_secret
|
||||
# (the secret part being at least 3 chars to avoid catching some lines like just "db_pwd=")
|
||||
match = re.search(r'(pwd|pass|password|secret|key)=(\S{3,})$', record.strip())
|
||||
# For 'key', we require to at least have one word char [a-zA-Z0-9_] before it to avoid catching "--key" used in many helpers
|
||||
match = re.search(r'(pwd|pass|password|secret|\wkey|token)=(\S{3,})$', record.strip())
|
||||
if match and match.group(2) not in self.data_to_redact:
|
||||
self.data_to_redact.append(match.group(2))
|
||||
except Exception as e:
|
||||
|
@ -502,7 +503,10 @@ class OperationLogger(object):
|
|||
The missing of the message below could help to see an electrical
|
||||
shortage.
|
||||
"""
|
||||
self.error(m18n.n('log_operation_unit_unclosed_properly'))
|
||||
if self.ended_at is not None or self.started_at is None:
|
||||
return
|
||||
else:
|
||||
self.error(m18n.n('log_operation_unit_unclosed_properly'))
|
||||
|
||||
|
||||
def _get_description_from_name(name):
|
||||
|
|
|
@ -1,740 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" License
|
||||
|
||||
Copyright (C) 2013 YunoHost
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
|
||||
""" yunohost_monitor.py
|
||||
|
||||
Monitoring functions
|
||||
"""
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
import psutil
|
||||
import calendar
|
||||
import subprocess
|
||||
import xmlrpclib
|
||||
import os.path
|
||||
import os
|
||||
import dns.resolver
|
||||
import cPickle as pickle
|
||||
from datetime import datetime
|
||||
|
||||
from moulinette import m18n
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
|
||||
from yunohost.utils.network import get_public_ip
|
||||
from yunohost.domain import _get_maindomain
|
||||
|
||||
logger = getActionLogger('yunohost.monitor')
|
||||
|
||||
GLANCES_URI = 'http://127.0.0.1:61209'
|
||||
STATS_PATH = '/var/lib/yunohost/stats'
|
||||
CRONTAB_PATH = '/etc/cron.d/yunohost-monitor'
|
||||
|
||||
|
||||
def monitor_disk(units=None, mountpoint=None, human_readable=False):
|
||||
"""
|
||||
Monitor disk space and usage
|
||||
|
||||
Keyword argument:
|
||||
units -- Unit(s) to monitor
|
||||
mountpoint -- Device mountpoint
|
||||
human_readable -- Print sizes in human readable format
|
||||
|
||||
"""
|
||||
glances = _get_glances_api()
|
||||
result_dname = None
|
||||
result = {}
|
||||
|
||||
if units is None:
|
||||
units = ['io', 'filesystem']
|
||||
|
||||
_format_dname = lambda d: (os.path.realpath(d)).replace('/dev/', '')
|
||||
|
||||
# Get mounted devices
|
||||
devices = {}
|
||||
for p in psutil.disk_partitions(all=True):
|
||||
if not p.device.startswith('/dev/') or not p.mountpoint:
|
||||
continue
|
||||
if mountpoint is None:
|
||||
devices[_format_dname(p.device)] = p.mountpoint
|
||||
elif mountpoint == p.mountpoint:
|
||||
dn = _format_dname(p.device)
|
||||
devices[dn] = p.mountpoint
|
||||
result_dname = dn
|
||||
if len(devices) == 0:
|
||||
if mountpoint is not None:
|
||||
raise YunohostError('mountpoint_unknown')
|
||||
return result
|
||||
|
||||
# Retrieve monitoring for unit(s)
|
||||
for u in units:
|
||||
if u == 'io':
|
||||
# Define setter
|
||||
if len(units) > 1:
|
||||
def _set(dn, dvalue):
|
||||
try:
|
||||
result[dn][u] = dvalue
|
||||
except KeyError:
|
||||
result[dn] = {u: dvalue}
|
||||
else:
|
||||
def _set(dn, dvalue):
|
||||
result[dn] = dvalue
|
||||
|
||||
# Iterate over values
|
||||
devices_names = devices.keys()
|
||||
for d in json.loads(glances.getDiskIO()):
|
||||
dname = d.pop('disk_name')
|
||||
try:
|
||||
devices_names.remove(dname)
|
||||
except:
|
||||
continue
|
||||
else:
|
||||
_set(dname, d)
|
||||
for dname in devices_names:
|
||||
_set(dname, 'not-available')
|
||||
elif u == 'filesystem':
|
||||
# Define setter
|
||||
if len(units) > 1:
|
||||
def _set(dn, dvalue):
|
||||
try:
|
||||
result[dn][u] = dvalue
|
||||
except KeyError:
|
||||
result[dn] = {u: dvalue}
|
||||
else:
|
||||
def _set(dn, dvalue):
|
||||
result[dn] = dvalue
|
||||
|
||||
# Iterate over values
|
||||
devices_names = devices.keys()
|
||||
for d in json.loads(glances.getFs()):
|
||||
dname = _format_dname(d.pop('device_name'))
|
||||
try:
|
||||
devices_names.remove(dname)
|
||||
except:
|
||||
continue
|
||||
else:
|
||||
d['avail'] = d['size'] - d['used']
|
||||
if human_readable:
|
||||
for i in ['used', 'avail', 'size']:
|
||||
d[i] = binary_to_human(d[i]) + 'B'
|
||||
_set(dname, d)
|
||||
for dname in devices_names:
|
||||
_set(dname, 'not-available')
|
||||
else:
|
||||
raise YunohostError('unit_unknown', unit=u)
|
||||
|
||||
if result_dname is not None:
|
||||
return result[result_dname]
|
||||
return result
|
||||
|
||||
|
||||
def monitor_network(units=None, human_readable=False):
|
||||
"""
|
||||
Monitor network interfaces
|
||||
|
||||
Keyword argument:
|
||||
units -- Unit(s) to monitor
|
||||
human_readable -- Print sizes in human readable format
|
||||
|
||||
"""
|
||||
glances = _get_glances_api()
|
||||
result = {}
|
||||
|
||||
if units is None:
|
||||
units = ['check', 'usage', 'infos']
|
||||
|
||||
# Get network devices and their addresses
|
||||
# TODO / FIXME : use functions in utils/network.py to manage this
|
||||
devices = {}
|
||||
output = subprocess.check_output('ip addr show'.split())
|
||||
for d in re.split('^(?:[0-9]+: )', output, flags=re.MULTILINE):
|
||||
# Extract device name (1) and its addresses (2)
|
||||
m = re.match('([^\s@]+)(?:@[\S]+)?: (.*)', d, flags=re.DOTALL)
|
||||
if m:
|
||||
devices[m.group(1)] = m.group(2)
|
||||
|
||||
# Retrieve monitoring for unit(s)
|
||||
for u in units:
|
||||
if u == 'check':
|
||||
result[u] = {}
|
||||
domain = _get_maindomain()
|
||||
cmd_check_smtp = os.system('/bin/nc -z -w1 yunohost.org 25')
|
||||
if cmd_check_smtp == 0:
|
||||
smtp_check = m18n.n('network_check_smtp_ok')
|
||||
else:
|
||||
smtp_check = m18n.n('network_check_smtp_ko')
|
||||
|
||||
try:
|
||||
answers = dns.resolver.query(domain, 'MX')
|
||||
mx_check = {}
|
||||
i = 0
|
||||
for server in answers:
|
||||
mx_id = 'mx%s' % i
|
||||
mx_check[mx_id] = server
|
||||
i = i + 1
|
||||
except:
|
||||
mx_check = m18n.n('network_check_mx_ko')
|
||||
result[u] = {
|
||||
'smtp_check': smtp_check,
|
||||
'mx_check': mx_check
|
||||
}
|
||||
elif u == 'usage':
|
||||
result[u] = {}
|
||||
for i in json.loads(glances.getNetwork()):
|
||||
iname = i['interface_name']
|
||||
if iname in devices.keys():
|
||||
del i['interface_name']
|
||||
if human_readable:
|
||||
for k in i.keys():
|
||||
if k != 'time_since_update':
|
||||
i[k] = binary_to_human(i[k]) + 'B'
|
||||
result[u][iname] = i
|
||||
else:
|
||||
logger.debug('interface name %s was not found', iname)
|
||||
elif u == 'infos':
|
||||
p_ipv4 = get_public_ip() or 'unknown'
|
||||
|
||||
# TODO / FIXME : use functions in utils/network.py to manage this
|
||||
l_ip = 'unknown'
|
||||
for name, addrs in devices.items():
|
||||
if name == 'lo':
|
||||
continue
|
||||
if not isinstance(l_ip, dict):
|
||||
l_ip = {}
|
||||
l_ip[name] = _extract_inet(addrs)
|
||||
|
||||
gateway = 'unknown'
|
||||
output = subprocess.check_output('ip route show'.split())
|
||||
m = re.search('default via (.*) dev ([a-z]+[0-9]?)', output)
|
||||
if m:
|
||||
addr = _extract_inet(m.group(1), True)
|
||||
if len(addr) == 1:
|
||||
proto, gateway = addr.popitem()
|
||||
|
||||
result[u] = {
|
||||
'public_ip': p_ipv4,
|
||||
'local_ip': l_ip,
|
||||
'gateway': gateway,
|
||||
}
|
||||
else:
|
||||
raise YunohostError('unit_unknown', unit=u)
|
||||
|
||||
if len(units) == 1:
|
||||
return result[units[0]]
|
||||
return result
|
||||
|
||||
|
||||
def monitor_system(units=None, human_readable=False):
|
||||
"""
|
||||
Monitor system informations and usage
|
||||
|
||||
Keyword argument:
|
||||
units -- Unit(s) to monitor
|
||||
human_readable -- Print sizes in human readable format
|
||||
|
||||
"""
|
||||
glances = _get_glances_api()
|
||||
result = {}
|
||||
|
||||
if units is None:
|
||||
units = ['memory', 'cpu', 'process', 'uptime', 'infos']
|
||||
|
||||
# Retrieve monitoring for unit(s)
|
||||
for u in units:
|
||||
if u == 'memory':
|
||||
ram = json.loads(glances.getMem())
|
||||
swap = json.loads(glances.getMemSwap())
|
||||
if human_readable:
|
||||
for i in ram.keys():
|
||||
if i != 'percent':
|
||||
ram[i] = binary_to_human(ram[i]) + 'B'
|
||||
for i in swap.keys():
|
||||
if i != 'percent':
|
||||
swap[i] = binary_to_human(swap[i]) + 'B'
|
||||
result[u] = {
|
||||
'ram': ram,
|
||||
'swap': swap
|
||||
}
|
||||
elif u == 'cpu':
|
||||
result[u] = {
|
||||
'load': json.loads(glances.getLoad()),
|
||||
'usage': json.loads(glances.getCpu())
|
||||
}
|
||||
elif u == 'process':
|
||||
result[u] = json.loads(glances.getProcessCount())
|
||||
elif u == 'uptime':
|
||||
result[u] = (str(datetime.now() - datetime.fromtimestamp(psutil.boot_time())).split('.')[0])
|
||||
elif u == 'infos':
|
||||
result[u] = json.loads(glances.getSystem())
|
||||
else:
|
||||
raise YunohostError('unit_unknown', unit=u)
|
||||
|
||||
if len(units) == 1 and not isinstance(result[units[0]], str):
|
||||
return result[units[0]]
|
||||
return result
|
||||
|
||||
|
||||
def monitor_update_stats(period):
|
||||
"""
|
||||
Update monitoring statistics
|
||||
|
||||
Keyword argument:
|
||||
period -- Time period to update (day, week, month)
|
||||
|
||||
"""
|
||||
if period not in ['day', 'week', 'month']:
|
||||
raise YunohostError('monitor_period_invalid')
|
||||
|
||||
stats = _retrieve_stats(period)
|
||||
if not stats:
|
||||
stats = {'disk': {}, 'network': {}, 'system': {}, 'timestamp': []}
|
||||
|
||||
monitor = None
|
||||
# Get monitoring stats
|
||||
if period == 'day':
|
||||
monitor = _monitor_all('day')
|
||||
else:
|
||||
t = stats['timestamp']
|
||||
p = 'day' if period == 'week' else 'week'
|
||||
if len(t) > 0:
|
||||
monitor = _monitor_all(p, t[len(t) - 1])
|
||||
else:
|
||||
monitor = _monitor_all(p, 0)
|
||||
if not monitor:
|
||||
raise YunohostError('monitor_stats_no_update')
|
||||
|
||||
stats['timestamp'].append(time.time())
|
||||
|
||||
# Append disk stats
|
||||
for dname, units in monitor['disk'].items():
|
||||
disk = {}
|
||||
# Retrieve current stats for disk name
|
||||
if dname in stats['disk'].keys():
|
||||
disk = stats['disk'][dname]
|
||||
|
||||
for unit, values in units.items():
|
||||
# Continue if unit doesn't contain stats
|
||||
if not isinstance(values, dict):
|
||||
continue
|
||||
|
||||
# Retrieve current stats for unit and append new ones
|
||||
curr = disk[unit] if unit in disk.keys() else {}
|
||||
if unit == 'io':
|
||||
disk[unit] = _append_to_stats(curr, values, 'time_since_update')
|
||||
elif unit == 'filesystem':
|
||||
disk[unit] = _append_to_stats(curr, values, ['fs_type', 'mnt_point'])
|
||||
stats['disk'][dname] = disk
|
||||
|
||||
# Append network stats
|
||||
net_usage = {}
|
||||
for iname, values in monitor['network']['usage'].items():
|
||||
# Continue if units doesn't contain stats
|
||||
if not isinstance(values, dict):
|
||||
continue
|
||||
|
||||
# Retrieve current stats and append new ones
|
||||
curr = {}
|
||||
if 'usage' in stats['network'] and iname in stats['network']['usage']:
|
||||
curr = stats['network']['usage'][iname]
|
||||
net_usage[iname] = _append_to_stats(curr, values, 'time_since_update')
|
||||
stats['network'] = {'usage': net_usage, 'infos': monitor['network']['infos']}
|
||||
|
||||
# Append system stats
|
||||
for unit, values in monitor['system'].items():
|
||||
# Continue if units doesn't contain stats
|
||||
if not isinstance(values, dict):
|
||||
continue
|
||||
|
||||
# Set static infos unit
|
||||
if unit == 'infos':
|
||||
stats['system'][unit] = values
|
||||
continue
|
||||
|
||||
# Retrieve current stats and append new ones
|
||||
curr = stats['system'][unit] if unit in stats['system'].keys() else {}
|
||||
stats['system'][unit] = _append_to_stats(curr, values)
|
||||
|
||||
_save_stats(stats, period)
|
||||
|
||||
|
||||
def monitor_show_stats(period, date=None):
|
||||
"""
|
||||
Show monitoring statistics
|
||||
|
||||
Keyword argument:
|
||||
period -- Time period to show (day, week, month)
|
||||
|
||||
"""
|
||||
if period not in ['day', 'week', 'month']:
|
||||
raise YunohostError('monitor_period_invalid')
|
||||
|
||||
result = _retrieve_stats(period, date)
|
||||
if result is False:
|
||||
raise YunohostError('monitor_stats_file_not_found')
|
||||
elif result is None:
|
||||
raise YunohostError('monitor_stats_period_unavailable')
|
||||
return result
|
||||
|
||||
|
||||
def monitor_enable(with_stats=False):
|
||||
"""
|
||||
Enable server monitoring
|
||||
|
||||
Keyword argument:
|
||||
with_stats -- Enable monitoring statistics
|
||||
|
||||
"""
|
||||
from yunohost.service import (service_status, service_enable,
|
||||
service_start)
|
||||
|
||||
glances = service_status('glances')
|
||||
if glances['status'] != 'running':
|
||||
service_start('glances')
|
||||
if glances['loaded'] != 'enabled':
|
||||
service_enable('glances')
|
||||
|
||||
# Install crontab
|
||||
if with_stats:
|
||||
# day: every 5 min # week: every 1 h # month: every 4 h #
|
||||
rules = ('*/5 * * * * root {cmd} day >> /dev/null\n'
|
||||
'3 * * * * root {cmd} week >> /dev/null\n'
|
||||
'6 */4 * * * root {cmd} month >> /dev/null').format(
|
||||
cmd='/usr/bin/yunohost --quiet monitor update-stats')
|
||||
with open(CRONTAB_PATH, 'w') as f:
|
||||
f.write(rules)
|
||||
|
||||
logger.success(m18n.n('monitor_enabled'))
|
||||
|
||||
|
||||
def monitor_disable():
|
||||
"""
|
||||
Disable server monitoring
|
||||
|
||||
"""
|
||||
from yunohost.service import (service_status, service_disable,
|
||||
service_stop)
|
||||
|
||||
glances = service_status('glances')
|
||||
if glances['status'] != 'inactive':
|
||||
service_stop('glances')
|
||||
if glances['loaded'] != 'disabled':
|
||||
try:
|
||||
service_disable('glances')
|
||||
except YunohostError as e:
|
||||
logger.warning(e.strerror)
|
||||
|
||||
# Remove crontab
|
||||
try:
|
||||
os.remove(CRONTAB_PATH)
|
||||
except:
|
||||
pass
|
||||
|
||||
logger.success(m18n.n('monitor_disabled'))
|
||||
|
||||
|
||||
def _get_glances_api():
|
||||
"""
|
||||
Retrieve Glances API running on the local server
|
||||
|
||||
"""
|
||||
try:
|
||||
p = xmlrpclib.ServerProxy(GLANCES_URI)
|
||||
p.system.methodHelp('getAll')
|
||||
except (xmlrpclib.ProtocolError, IOError):
|
||||
pass
|
||||
else:
|
||||
return p
|
||||
|
||||
from yunohost.service import service_status
|
||||
|
||||
if service_status('glances')['status'] != 'running':
|
||||
raise YunohostError('monitor_not_enabled')
|
||||
raise YunohostError('monitor_glances_con_failed')
|
||||
|
||||
|
||||
def _extract_inet(string, skip_netmask=False, skip_loopback=True):
|
||||
"""
|
||||
Extract IP addresses (v4 and/or v6) from a string limited to one
|
||||
address by protocol
|
||||
|
||||
Keyword argument:
|
||||
string -- String to search in
|
||||
skip_netmask -- True to skip subnet mask extraction
|
||||
skip_loopback -- False to include addresses reserved for the
|
||||
loopback interface
|
||||
|
||||
Returns:
|
||||
A dict of {protocol: address} with protocol one of 'ipv4' or 'ipv6'
|
||||
|
||||
"""
|
||||
ip4_pattern = '((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}'
|
||||
ip6_pattern = '(((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)'
|
||||
ip4_pattern += '/[0-9]{1,2})' if not skip_netmask else ')'
|
||||
ip6_pattern += '/[0-9]{1,3})' if not skip_netmask else ')'
|
||||
result = {}
|
||||
|
||||
for m in re.finditer(ip4_pattern, string):
|
||||
addr = m.group(1)
|
||||
if skip_loopback and addr.startswith('127.'):
|
||||
continue
|
||||
|
||||
# Limit to only one result
|
||||
result['ipv4'] = addr
|
||||
break
|
||||
|
||||
for m in re.finditer(ip6_pattern, string):
|
||||
addr = m.group(1)
|
||||
if skip_loopback and addr == '::1':
|
||||
continue
|
||||
|
||||
# Limit to only one result
|
||||
result['ipv6'] = addr
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
|
||||
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%s' % (value, s)
|
||||
return "%s" % n
|
||||
|
||||
|
||||
def _retrieve_stats(period, date=None):
|
||||
"""
|
||||
Retrieve statistics from pickle file
|
||||
|
||||
Keyword argument:
|
||||
period -- Time period to retrieve (day, week, month)
|
||||
date -- Date of stats to retrieve
|
||||
|
||||
"""
|
||||
pkl_file = None
|
||||
|
||||
# Retrieve pickle file
|
||||
if date is not None:
|
||||
timestamp = calendar.timegm(date)
|
||||
pkl_file = '%s/%d_%s.pkl' % (STATS_PATH, timestamp, period)
|
||||
else:
|
||||
pkl_file = '%s/%s.pkl' % (STATS_PATH, period)
|
||||
if not os.path.isfile(pkl_file):
|
||||
return False
|
||||
|
||||
# Read file and process its content
|
||||
with open(pkl_file, 'r') as f:
|
||||
result = pickle.load(f)
|
||||
if not isinstance(result, dict):
|
||||
return None
|
||||
return result
|
||||
|
||||
|
||||
def _save_stats(stats, period, date=None):
|
||||
"""
|
||||
Save statistics to pickle file
|
||||
|
||||
Keyword argument:
|
||||
stats -- Stats dict to save
|
||||
period -- Time period of stats (day, week, month)
|
||||
date -- Date of stats
|
||||
|
||||
"""
|
||||
pkl_file = None
|
||||
|
||||
# Set pickle file name
|
||||
if date is not None:
|
||||
timestamp = calendar.timegm(date)
|
||||
pkl_file = '%s/%d_%s.pkl' % (STATS_PATH, timestamp, period)
|
||||
else:
|
||||
pkl_file = '%s/%s.pkl' % (STATS_PATH, period)
|
||||
if not os.path.isdir(STATS_PATH):
|
||||
os.makedirs(STATS_PATH)
|
||||
|
||||
# Limit stats
|
||||
if date is None:
|
||||
t = stats['timestamp']
|
||||
limit = {'day': 86400, 'week': 604800, 'month': 2419200}
|
||||
if (t[len(t) - 1] - t[0]) > limit[period]:
|
||||
begin = t[len(t) - 1] - limit[period]
|
||||
stats = _filter_stats(stats, begin)
|
||||
|
||||
# Write file content
|
||||
with open(pkl_file, 'w') as f:
|
||||
pickle.dump(stats, f)
|
||||
return True
|
||||
|
||||
|
||||
def _monitor_all(period=None, since=None):
|
||||
"""
|
||||
Monitor all units (disk, network and system) for the given period
|
||||
If since is None, real-time monitoring is returned. Otherwise, the
|
||||
mean of stats since this timestamp is calculated and returned.
|
||||
|
||||
Keyword argument:
|
||||
period -- Time period to monitor (day, week, month)
|
||||
since -- Timestamp of the stats beginning
|
||||
|
||||
"""
|
||||
result = {'disk': {}, 'network': {}, 'system': {}}
|
||||
|
||||
# Real-time stats
|
||||
if period == 'day' and since is None:
|
||||
result['disk'] = monitor_disk()
|
||||
result['network'] = monitor_network()
|
||||
result['system'] = monitor_system()
|
||||
return result
|
||||
|
||||
# Retrieve stats and calculate mean
|
||||
stats = _retrieve_stats(period)
|
||||
if not stats:
|
||||
return None
|
||||
stats = _filter_stats(stats, since)
|
||||
if not stats:
|
||||
return None
|
||||
result = _calculate_stats_mean(stats)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def _filter_stats(stats, t_begin=None, t_end=None):
|
||||
"""
|
||||
Filter statistics by beginning and/or ending timestamp
|
||||
|
||||
Keyword argument:
|
||||
stats -- Dict stats to filter
|
||||
t_begin -- Beginning timestamp
|
||||
t_end -- Ending timestamp
|
||||
|
||||
"""
|
||||
if t_begin is None and t_end is None:
|
||||
return stats
|
||||
|
||||
i_begin = i_end = None
|
||||
# Look for indexes of timestamp interval
|
||||
for i, t in enumerate(stats['timestamp']):
|
||||
if t_begin and i_begin is None and t >= t_begin:
|
||||
i_begin = i
|
||||
if t_end and i != 0 and i_end is None and t > t_end:
|
||||
i_end = i
|
||||
# Check indexes
|
||||
if i_begin is None:
|
||||
if t_begin and t_begin > stats['timestamp'][0]:
|
||||
return None
|
||||
i_begin = 0
|
||||
if i_end is None:
|
||||
if t_end and t_end < stats['timestamp'][0]:
|
||||
return None
|
||||
i_end = len(stats['timestamp'])
|
||||
if i_begin == 0 and i_end == len(stats['timestamp']):
|
||||
return stats
|
||||
|
||||
# Filter function
|
||||
def _filter(s, i, j):
|
||||
for k, v in s.items():
|
||||
if isinstance(v, dict):
|
||||
s[k] = _filter(v, i, j)
|
||||
elif isinstance(v, list):
|
||||
s[k] = v[i:j]
|
||||
return s
|
||||
|
||||
stats = _filter(stats, i_begin, i_end)
|
||||
return stats
|
||||
|
||||
|
||||
def _calculate_stats_mean(stats):
|
||||
"""
|
||||
Calculate the weighted mean for each statistic
|
||||
|
||||
Keyword argument:
|
||||
stats -- Stats dict to process
|
||||
|
||||
"""
|
||||
timestamp = stats['timestamp']
|
||||
t_sum = sum(timestamp)
|
||||
del stats['timestamp']
|
||||
|
||||
# Weighted mean function
|
||||
def _mean(s, t, ts):
|
||||
for k, v in s.items():
|
||||
if isinstance(v, dict):
|
||||
s[k] = _mean(v, t, ts)
|
||||
elif isinstance(v, list):
|
||||
try:
|
||||
nums = [float(x * t[i]) for i, x in enumerate(v)]
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
s[k] = sum(nums) / float(ts)
|
||||
return s
|
||||
|
||||
stats = _mean(stats, timestamp, t_sum)
|
||||
return stats
|
||||
|
||||
|
||||
def _append_to_stats(stats, monitor, statics=[]):
|
||||
"""
|
||||
Append monitoring statistics to current statistics
|
||||
|
||||
Keyword argument:
|
||||
stats -- Current stats dict
|
||||
monitor -- Monitoring statistics
|
||||
statics -- List of stats static keys
|
||||
|
||||
"""
|
||||
if isinstance(statics, str):
|
||||
statics = [statics]
|
||||
|
||||
# Appending function
|
||||
def _append(s, m, st):
|
||||
for k, v in m.items():
|
||||
if k in st:
|
||||
s[k] = v
|
||||
elif isinstance(v, dict):
|
||||
if k not in s:
|
||||
s[k] = {}
|
||||
s[k] = _append(s[k], v, st)
|
||||
else:
|
||||
if k not in s:
|
||||
s[k] = []
|
||||
if isinstance(v, list):
|
||||
s[k].extend(v)
|
||||
else:
|
||||
s[k].append(v)
|
||||
return s
|
||||
|
||||
stats = _append(stats, monitor, statics)
|
||||
return stats
|
|
@ -24,6 +24,7 @@
|
|||
Manage permissions
|
||||
"""
|
||||
|
||||
import copy
|
||||
import grp
|
||||
import random
|
||||
|
||||
|
@ -35,309 +36,268 @@ from yunohost.log import is_unit_operation
|
|||
|
||||
logger = getActionLogger('yunohost.user')
|
||||
|
||||
SYSTEM_PERMS = ["mail", "xmpp", "stfp"]
|
||||
|
||||
def user_permission_list(app=None, permission=None, username=None, group=None):
|
||||
#
|
||||
#
|
||||
# The followings are the methods exposed through the "yunohost user permission" interface
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
def user_permission_list(short=False, full=False, ignore_system_perms=False):
|
||||
"""
|
||||
List permission for specific application
|
||||
|
||||
Keyword argument:
|
||||
app -- an application OR sftp, xmpp (metronome), mail
|
||||
permission -- name of the permission ("main" by default)
|
||||
username -- Username to get informations
|
||||
group -- Groupname to get informations
|
||||
|
||||
List permissions and corresponding accesses
|
||||
"""
|
||||
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
# Fetch relevant informations
|
||||
|
||||
from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract
|
||||
ldap = _get_ldap_interface()
|
||||
permissions_infos = ldap.search('ou=permission,dc=yunohost,dc=org',
|
||||
'(objectclass=permissionYnh)',
|
||||
["cn", 'groupPermission', 'inheritPermission', 'URL'])
|
||||
|
||||
permission_attrs = [
|
||||
'cn',
|
||||
'groupPermission',
|
||||
'inheritPermission',
|
||||
'URL',
|
||||
]
|
||||
|
||||
# Normally app is alway defined but it should be possible to set it
|
||||
if app and not isinstance(app, list):
|
||||
app = [app]
|
||||
if permission and not isinstance(permission, list):
|
||||
permission = [permission]
|
||||
if not isinstance(username, list):
|
||||
username = [username]
|
||||
if not isinstance(group, list):
|
||||
group = [group]
|
||||
# Parse / organize information to be outputed
|
||||
|
||||
permissions = {}
|
||||
for infos in permissions_infos:
|
||||
|
||||
result = ldap.search('ou=permission,dc=yunohost,dc=org',
|
||||
'(objectclass=permissionYnh)', permission_attrs)
|
||||
name = infos['cn'][0]
|
||||
|
||||
for res in result:
|
||||
try:
|
||||
permission_name, app_name = res['cn'][0].split('.')
|
||||
except:
|
||||
logger.warning(m18n.n('permission_name_not_valid', permission=res['cn'][0]))
|
||||
group_name = []
|
||||
if 'groupPermission' in res:
|
||||
for g in res['groupPermission']:
|
||||
group_name.append(g.split("=")[1].split(",")[0])
|
||||
user_name = []
|
||||
if 'inheritPermission' in res:
|
||||
for u in res['inheritPermission']:
|
||||
user_name.append(u.split("=")[1].split(",")[0])
|
||||
|
||||
# Don't show the result if the user defined a specific permission, user or group
|
||||
if app and app_name not in app:
|
||||
continue
|
||||
if permission and permission_name not in permission:
|
||||
continue
|
||||
if username[0] and not set(username) & set(user_name):
|
||||
continue
|
||||
if group[0] and not set(group) & set(group_name):
|
||||
if ignore_system_perms and name.split(".")[0] in SYSTEM_PERMS:
|
||||
continue
|
||||
|
||||
if app_name not in permissions:
|
||||
permissions[app_name] = {}
|
||||
permissions[name] = {}
|
||||
permissions[name]["allowed"] = [_ldap_path_extract(p, "cn") for p in infos.get('groupPermission', [])]
|
||||
|
||||
permissions[app_name][permission_name] = {'allowed_users': [], 'allowed_groups': []}
|
||||
for g in group_name:
|
||||
permissions[app_name][permission_name]['allowed_groups'].append(g)
|
||||
for u in user_name:
|
||||
permissions[app_name][permission_name]['allowed_users'].append(u)
|
||||
if 'URL' in res:
|
||||
permissions[app_name][permission_name]['URL'] = []
|
||||
for u in res['URL']:
|
||||
permissions[app_name][permission_name]['URL'].append(u)
|
||||
if full:
|
||||
permissions[name]["corresponding_users"] = [_ldap_path_extract(p, "uid") for p in infos.get('inheritPermission', [])]
|
||||
permissions[name]["url"] = infos.get("URL", [None])[0]
|
||||
|
||||
if short:
|
||||
permissions = permissions.keys()
|
||||
|
||||
return {'permissions': permissions}
|
||||
|
||||
|
||||
def user_permission_update(operation_logger, app=[], permission=None, add_username=None, add_group=None, del_username=None, del_group=None, sync_perm=True):
|
||||
@is_unit_operation()
|
||||
def user_permission_update(operation_logger, permission, add=None, remove=None, sync_perm=True):
|
||||
"""
|
||||
Allow or Disallow a user or group to a permission for a specific application
|
||||
|
||||
Keyword argument:
|
||||
app -- an application OR sftp, xmpp (metronome), mail
|
||||
permission -- name of the permission ("main" by default)
|
||||
add_username -- Username to allow
|
||||
add_group -- Groupname to allow
|
||||
del_username -- Username to disallow
|
||||
del_group -- Groupname to disallow
|
||||
|
||||
permission -- Name of the permission (e.g. mail or or wordpress or wordpress.editors)
|
||||
add -- List of groups or usernames to add to this permission
|
||||
remove -- List of groups or usernames to remove from to this permission
|
||||
"""
|
||||
from yunohost.hook import hook_callback
|
||||
from yunohost.user import user_group_list
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
if permission:
|
||||
if not isinstance(permission, list):
|
||||
permission = [permission]
|
||||
else:
|
||||
permission = ["main"]
|
||||
# By default, manipulate main permission
|
||||
if "." not in permission:
|
||||
permission = permission + ".main"
|
||||
|
||||
if add_group:
|
||||
if not isinstance(add_group, list):
|
||||
add_group = [add_group]
|
||||
else:
|
||||
add_group = []
|
||||
# Refuse to add "visitors" to mail, xmpp ... they require an account to make sense.
|
||||
if add and "visitors" in add and permission.split(".")[0] in SYSTEM_PERMS:
|
||||
raise YunohostError('permission_require_account', permission=permission)
|
||||
|
||||
if add_username:
|
||||
if not isinstance(add_username, list):
|
||||
add_username = [add_username]
|
||||
else:
|
||||
add_username = []
|
||||
# Fetch currently allowed groups for this permission
|
||||
|
||||
if del_group:
|
||||
if not isinstance(del_group, list):
|
||||
del_group = [del_group]
|
||||
else:
|
||||
del_group = []
|
||||
existing_permission = user_permission_list(full=True)["permissions"].get(permission, None)
|
||||
if existing_permission is None:
|
||||
raise YunohostError('permission_not_found', permission=permission)
|
||||
|
||||
if del_username:
|
||||
if not isinstance(del_username, list):
|
||||
del_username = [del_username]
|
||||
else:
|
||||
del_username = []
|
||||
current_allowed_groups = existing_permission["allowed"]
|
||||
all_existing_groups = user_group_list()['groups'].keys()
|
||||
operation_logger.related_to.append(('app', permission.split(".")[0]))
|
||||
|
||||
# Validate that the group exist
|
||||
for g in add_group:
|
||||
if g not in user_group_list(['cn'])['groups']:
|
||||
raise YunohostError('group_unknown', group=g)
|
||||
for u in add_username:
|
||||
if u not in user_list(['uid'])['users']:
|
||||
raise YunohostError('user_unknown', user=u)
|
||||
for g in del_group:
|
||||
if g not in user_group_list(['cn'])['groups']:
|
||||
raise YunohostError('group_unknown', group=g)
|
||||
for u in del_username:
|
||||
if u not in user_list(['uid'])['users']:
|
||||
raise YunohostError('user_unknown', user=u)
|
||||
# Compute new allowed group list (and make sure what we're doing make sense)
|
||||
|
||||
# Merge user and group (note that we consider all user as a group)
|
||||
add_group.extend(add_username)
|
||||
del_group.extend(del_username)
|
||||
new_allowed_groups = copy.copy(current_allowed_groups)
|
||||
|
||||
if 'all_users' in add_group or 'all_users' in del_group:
|
||||
raise YunohostError('edit_permission_with_group_all_users_not_allowed')
|
||||
if add:
|
||||
groups_to_add = [add] if not isinstance(add, list) else add
|
||||
for group in groups_to_add:
|
||||
if group not in all_existing_groups:
|
||||
raise YunohostError('group_unknown', group=group)
|
||||
if group in current_allowed_groups:
|
||||
logger.warning(m18n.n('permission_already_allowed', permission=permission, group=group))
|
||||
else:
|
||||
operation_logger.related_to.append(('group', group))
|
||||
|
||||
# Populate permission informations
|
||||
permission_attrs = [
|
||||
'cn',
|
||||
'groupPermission',
|
||||
]
|
||||
result = ldap.search('ou=permission,dc=yunohost,dc=org',
|
||||
'(objectclass=permissionYnh)', permission_attrs)
|
||||
result = {p['cn'][0]: p for p in result}
|
||||
new_allowed_groups += groups_to_add
|
||||
|
||||
new_per_dict = {}
|
||||
if remove:
|
||||
groups_to_remove = [remove] if not isinstance(remove, list) else remove
|
||||
for group in groups_to_remove:
|
||||
if group not in all_existing_groups:
|
||||
raise YunohostError('group_unknown', group=group)
|
||||
if group not in current_allowed_groups:
|
||||
logger.warning(m18n.n('permission_already_disallowed', permission=permission, group=group))
|
||||
else:
|
||||
operation_logger.related_to.append(('group', group))
|
||||
|
||||
for a in app:
|
||||
for per in permission:
|
||||
permission_name = per + '.' + a
|
||||
if permission_name not in result:
|
||||
raise YunohostError('permission_not_found', permission=per, app=a)
|
||||
new_per_dict[permission_name] = set()
|
||||
if 'groupPermission' in result[permission_name]:
|
||||
new_per_dict[permission_name] = set(result[permission_name]['groupPermission'])
|
||||
new_allowed_groups = [g for g in new_allowed_groups if g not in groups_to_remove]
|
||||
|
||||
for g in del_group:
|
||||
if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]:
|
||||
raise YunohostError('need_define_permission_before')
|
||||
group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org'
|
||||
if group_name not in new_per_dict[permission_name]:
|
||||
logger.warning(m18n.n('group_already_disallowed', permission=per, app=a, group=g))
|
||||
else:
|
||||
new_per_dict[permission_name].remove(group_name)
|
||||
# If we end up with something like allowed groups is ["all_users", "volunteers"]
|
||||
# we shall warn the users that they should probably choose between one or the other,
|
||||
# because the current situation is probably not what they expect / is temporary ?
|
||||
|
||||
if 'cn=all_users,ou=groups,dc=yunohost,dc=org' in new_per_dict[permission_name]:
|
||||
new_per_dict[permission_name].remove('cn=all_users,ou=groups,dc=yunohost,dc=org')
|
||||
for g in add_group:
|
||||
group_name = 'cn=' + g + ',ou=groups,dc=yunohost,dc=org'
|
||||
if group_name in new_per_dict[permission_name]:
|
||||
logger.warning(m18n.n('group_already_allowed', permission=per, app=a, group=g))
|
||||
else:
|
||||
new_per_dict[permission_name].add(group_name)
|
||||
if len(new_allowed_groups) > 1:
|
||||
if "all_users" in new_allowed_groups:
|
||||
logger.warning(m18n.n("permission_currently_allowed_for_all_users"))
|
||||
if "visitors" in new_allowed_groups:
|
||||
logger.warning(m18n.n("permission_currently_allowed_for_visitors"))
|
||||
|
||||
# Don't update LDAP if we update exactly the same values
|
||||
if set(new_allowed_groups) == set(current_allowed_groups):
|
||||
logger.warning(m18n.n("permission_already_up_to_date"))
|
||||
return existing_permission
|
||||
|
||||
# Commit the new allowed group list
|
||||
|
||||
operation_logger.start()
|
||||
|
||||
for per, val in new_per_dict.items():
|
||||
# Don't update LDAP if we update exactly the same values
|
||||
if val == set(result[per]['groupPermission'] if 'groupPermission' in result[per] else []):
|
||||
continue
|
||||
if ldap.update('cn=%s,ou=permission' % per, {'groupPermission': val}):
|
||||
p = per.split('.')
|
||||
logger.debug(m18n.n('permission_updated', permission=p[0], app=p[1]))
|
||||
else:
|
||||
raise YunohostError('permission_update_failed')
|
||||
try:
|
||||
ldap.update('cn=%s,ou=permission' % permission,
|
||||
{'groupPermission': ['cn=' + g + ',ou=groups,dc=yunohost,dc=org' for g in new_allowed_groups]})
|
||||
except Exception as e:
|
||||
raise YunohostError('permission_update_failed', permission=permission, error=e)
|
||||
|
||||
logger.debug(m18n.n('permission_updated', permission=permission))
|
||||
|
||||
# Trigger permission sync if asked
|
||||
|
||||
if sync_perm:
|
||||
permission_sync_to_user()
|
||||
|
||||
for a in app:
|
||||
allowed_users = set()
|
||||
disallowed_users = set()
|
||||
group_list = user_group_list(['member'])['groups']
|
||||
new_permission = user_permission_list(full=True)["permissions"][permission]
|
||||
|
||||
for g in add_group:
|
||||
if 'members' in group_list[g]:
|
||||
allowed_users.union(group_list[g]['members'])
|
||||
for g in del_group:
|
||||
if 'members' in group_list[g]:
|
||||
disallowed_users.union(group_list[g]['members'])
|
||||
# Trigger app callbacks
|
||||
|
||||
allowed_users = ','.join(allowed_users)
|
||||
disallowed_users = ','.join(disallowed_users)
|
||||
if add_group:
|
||||
hook_callback('post_app_addaccess', args=[app, allowed_users])
|
||||
if del_group:
|
||||
hook_callback('post_app_removeaccess', args=[app, disallowed_users])
|
||||
app = permission.split(".")[0]
|
||||
|
||||
return user_permission_list(app, permission)
|
||||
old_allowed_users = set(existing_permission["corresponding_users"])
|
||||
new_allowed_users = set(new_permission["corresponding_users"])
|
||||
|
||||
effectively_added_users = new_allowed_users - old_allowed_users
|
||||
effectively_removed_users = old_allowed_users - new_allowed_users
|
||||
|
||||
if effectively_added_users:
|
||||
hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)])
|
||||
if effectively_removed_users:
|
||||
hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)])
|
||||
|
||||
return new_permission
|
||||
|
||||
|
||||
def user_permission_clear(operation_logger, app=[], permission=None, sync_perm=True):
|
||||
@is_unit_operation()
|
||||
def user_permission_reset(operation_logger, permission, sync_perm=True):
|
||||
"""
|
||||
Reset the permission for a specific application
|
||||
Reset a given permission to just 'all_users'
|
||||
|
||||
Keyword argument:
|
||||
app -- an application OR sftp, xmpp (metronome), mail
|
||||
permission -- name of the permission ("main" by default)
|
||||
username -- Username to get informations (all by default)
|
||||
group -- Groupname to get informations (all by default)
|
||||
|
||||
permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
|
||||
"""
|
||||
from yunohost.hook import hook_callback
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
if permission:
|
||||
if not isinstance(permission, list):
|
||||
permission = [permission]
|
||||
else:
|
||||
permission = ["main"]
|
||||
# By default, manipulate main permission
|
||||
if "." not in permission:
|
||||
permission = permission + ".main"
|
||||
|
||||
# Fetch existing permission
|
||||
|
||||
existing_permission = user_permission_list(full=True)["permissions"].get(permission, None)
|
||||
if existing_permission is None:
|
||||
raise YunohostError('permission_not_found', permission=permission)
|
||||
|
||||
if existing_permission["allowed"] == ["all_users"]:
|
||||
logger.warning(m18n.n("permission_already_up_to_date"))
|
||||
return
|
||||
|
||||
# Update permission with default (all_users)
|
||||
|
||||
operation_logger.related_to.append(('app', permission.split(".")[0]))
|
||||
operation_logger.start()
|
||||
|
||||
default_permission = {'groupPermission': ['cn=all_users,ou=groups,dc=yunohost,dc=org']}
|
||||
try:
|
||||
ldap.update('cn=%s,ou=permission' % permission, default_permission)
|
||||
except Exception as e:
|
||||
raise YunohostError('permission_update_failed', permission=permission, error=e)
|
||||
|
||||
# Populate permission informations
|
||||
permission_attrs = [
|
||||
'cn',
|
||||
'groupPermission',
|
||||
]
|
||||
result = ldap.search('ou=permission,dc=yunohost,dc=org',
|
||||
'(objectclass=permissionYnh)', permission_attrs)
|
||||
result = {p['cn'][0]: p for p in result}
|
||||
logger.debug(m18n.n('permission_updated', permission=permission))
|
||||
|
||||
for a in app:
|
||||
for per in permission:
|
||||
permission_name = per + '.' + a
|
||||
if permission_name not in result:
|
||||
raise YunohostError('permission_not_found', permission=per, app=a)
|
||||
if 'groupPermission' in result[permission_name] and 'cn=all_users,ou=groups,dc=yunohost,dc=org' in result[permission_name]['groupPermission']:
|
||||
logger.warning(m18n.n('permission_already_clear', permission=per, app=a))
|
||||
continue
|
||||
if ldap.update('cn=%s,ou=permission' % permission_name, default_permission):
|
||||
logger.debug(m18n.n('permission_updated', permission=per, app=a))
|
||||
else:
|
||||
raise YunohostError('permission_update_failed')
|
||||
if sync_perm:
|
||||
permission_sync_to_user()
|
||||
|
||||
permission_sync_to_user()
|
||||
new_permission = user_permission_list(full=True)["permissions"][permission]
|
||||
|
||||
for a in app:
|
||||
permission_name = 'main.' + a
|
||||
result = ldap.search('ou=permission,dc=yunohost,dc=org',
|
||||
filter='cn=' + permission_name, attrs=['inheritPermission'])
|
||||
if result:
|
||||
allowed_users = result[0]['inheritPermission']
|
||||
new_user_list = ','.join(allowed_users)
|
||||
hook_callback('post_app_removeaccess', args=[app, new_user_list])
|
||||
# Trigger app callbacks
|
||||
|
||||
return user_permission_list(app, permission)
|
||||
app = permission.split(".")[0]
|
||||
|
||||
old_allowed_users = set(existing_permission["corresponding_users"])
|
||||
new_allowed_users = set(new_permission["corresponding_users"])
|
||||
|
||||
effectively_added_users = new_allowed_users - old_allowed_users
|
||||
effectively_removed_users = old_allowed_users - new_allowed_users
|
||||
|
||||
if effectively_added_users:
|
||||
hook_callback('post_app_addaccess', args=[app, ','.join(effectively_added_users)])
|
||||
if effectively_removed_users:
|
||||
hook_callback('post_app_removeaccess', args=[app, ','.join(effectively_removed_users)])
|
||||
|
||||
return new_permission
|
||||
|
||||
#
|
||||
#
|
||||
# The followings methods are *not* directly exposed.
|
||||
# They are used to create/delete the permissions (e.g. during app install/remove)
|
||||
# and by some app helpers to possibly add additional permissions
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
@is_unit_operation(['permission', 'app'])
|
||||
def permission_add(operation_logger, app, permission, urls=None, default_allow=True, sync_perm=True):
|
||||
@is_unit_operation()
|
||||
def permission_create(operation_logger, permission, url=None, allowed=None, sync_perm=True):
|
||||
"""
|
||||
Create a new permission for a specific application
|
||||
|
||||
Keyword argument:
|
||||
app -- an application OR sftp, xmpp (metronome), mail
|
||||
permission -- name of the permission ("main" by default)
|
||||
urls -- list of urls to specify for the permission
|
||||
permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
|
||||
url -- (optional) URL for which access will be allowed/forbidden
|
||||
allowed -- (optional) A list of group/user to allow for the permission
|
||||
|
||||
If provided, 'url' is assumed to be relative to the app domain/path if they
|
||||
start with '/'. For example:
|
||||
/ -> domain.tld/app
|
||||
/admin -> domain.tld/app/admin
|
||||
domain.tld/app/api -> domain.tld/app/api
|
||||
|
||||
'url' can be later treated as a regex if it starts with "re:".
|
||||
For example:
|
||||
re:/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$
|
||||
re:domain.tld/app/api/[A-Z]*$ -> domain.tld/app/api/[A-Z]*$
|
||||
"""
|
||||
from yunohost.domain import _normalize_domain_path
|
||||
|
||||
from yunohost.user import user_group_list
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
# By default, manipulate main permission
|
||||
if "." not in permission:
|
||||
permission = permission + ".main"
|
||||
|
||||
# Validate uniqueness of permission in LDAP
|
||||
permission_name = str(permission + '.' + app) # str(...) Fix encoding issue
|
||||
conflict = ldap.get_conflict({
|
||||
'cn': permission_name
|
||||
}, base_dn='ou=permission,dc=yunohost,dc=org')
|
||||
if conflict:
|
||||
raise YunohostError('permission_already_exist', permission=permission, app=app)
|
||||
if ldap.get_conflict({'cn': permission},
|
||||
base_dn='ou=permission,dc=yunohost,dc=org'):
|
||||
raise YunohostError('permission_already_exist', permission=permission)
|
||||
|
||||
# Get random GID
|
||||
all_gid = {x.gr_gid for x in grp.getgrall()}
|
||||
|
@ -349,176 +309,162 @@ def permission_add(operation_logger, app, permission, urls=None, default_allow=T
|
|||
|
||||
attr_dict = {
|
||||
'objectClass': ['top', 'permissionYnh', 'posixGroup'],
|
||||
'cn': permission_name,
|
||||
'cn': str(permission),
|
||||
'gidNumber': gid,
|
||||
}
|
||||
if default_allow:
|
||||
attr_dict['groupPermission'] = 'cn=all_users,ou=groups,dc=yunohost,dc=org'
|
||||
|
||||
if urls:
|
||||
attr_dict['URL'] = []
|
||||
for url in urls:
|
||||
domain = url[:url.index('/')]
|
||||
path = url[url.index('/'):]
|
||||
domain, path = _normalize_domain_path(domain, path)
|
||||
attr_dict['URL'].append(domain + path)
|
||||
# If who should be allowed is explicitly provided, use this info
|
||||
if allowed:
|
||||
if not isinstance(allowed, list):
|
||||
allowed = [allowed]
|
||||
# (though first we validate that the targets actually exist)
|
||||
all_existing_groups = user_group_list()['groups'].keys()
|
||||
for g in allowed:
|
||||
if g not in all_existing_groups:
|
||||
raise YunohostError('group_unknown', group=g)
|
||||
attr_dict['groupPermission'] = ['cn=%s,ou=groups,dc=yunohost,dc=org' % g for g in allowed]
|
||||
# For main permission, we add all users by default
|
||||
elif permission.endswith(".main"):
|
||||
attr_dict['groupPermission'] = ['cn=all_users,ou=groups,dc=yunohost,dc=org']
|
||||
|
||||
if url:
|
||||
attr_dict['URL'] = url
|
||||
|
||||
operation_logger.related_to.append(('app', permission.split(".")[0]))
|
||||
operation_logger.start()
|
||||
if ldap.add('cn=%s,ou=permission' % permission_name, attr_dict):
|
||||
if sync_perm:
|
||||
permission_sync_to_user()
|
||||
logger.debug(m18n.n('permission_created', permission=permission, app=app))
|
||||
return user_permission_list(app, permission)
|
||||
|
||||
raise YunohostError('permission_creation_failed')
|
||||
try:
|
||||
ldap.add('cn=%s,ou=permission' % permission, attr_dict)
|
||||
except Exception as e:
|
||||
raise YunohostError('permission_creation_failed', permission=permission, error=e)
|
||||
|
||||
|
||||
@is_unit_operation(['permission', 'app'])
|
||||
def permission_update(operation_logger, app, permission, add_url=None, remove_url=None, sync_perm=True):
|
||||
"""
|
||||
Update a permission for a specific application
|
||||
|
||||
Keyword argument:
|
||||
app -- an application OR sftp, xmpp (metronome), mail
|
||||
permission -- name of the permission ("main" by default)
|
||||
add_url -- Add a new url for a permission
|
||||
remove_url -- Remove a url for a permission
|
||||
|
||||
"""
|
||||
from yunohost.domain import _normalize_domain_path
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
permission_name = str(permission + '.' + app) # str(...) Fix encoding issue
|
||||
|
||||
# Populate permission informations
|
||||
result = ldap.search(base='ou=permission,dc=yunohost,dc=org',
|
||||
filter='cn=' + permission_name, attrs=['URL'])
|
||||
if not result:
|
||||
raise YunohostError('permission_not_found', permission=permission, app=app)
|
||||
permission_obj = result[0]
|
||||
|
||||
if 'URL' not in permission_obj:
|
||||
permission_obj['URL'] = []
|
||||
|
||||
url = set(permission_obj['URL'])
|
||||
|
||||
if add_url:
|
||||
for u in add_url:
|
||||
domain = u[:u.index('/')]
|
||||
path = u[u.index('/'):]
|
||||
domain, path = _normalize_domain_path(domain, path)
|
||||
url.add(domain + path)
|
||||
if remove_url:
|
||||
for u in remove_url:
|
||||
domain = u[:u.index('/')]
|
||||
path = u[u.index('/'):]
|
||||
domain, path = _normalize_domain_path(domain, path)
|
||||
url.discard(domain + path)
|
||||
|
||||
if url == set(permission_obj['URL']):
|
||||
logger.warning(m18n.n('permission_update_nothing_to_do'))
|
||||
return user_permission_list(app, permission)
|
||||
|
||||
operation_logger.start()
|
||||
if ldap.update('cn=%s,ou=permission' % permission_name, {'cn': permission_name, 'URL': url}):
|
||||
if sync_perm:
|
||||
permission_sync_to_user()
|
||||
logger.debug(m18n.n('permission_updated', permission=permission, app=app))
|
||||
return user_permission_list(app, permission)
|
||||
|
||||
raise YunohostError('premission_update_failed')
|
||||
|
||||
|
||||
@is_unit_operation(['permission', 'app'])
|
||||
def permission_remove(operation_logger, app, permission, force=False, sync_perm=True):
|
||||
"""
|
||||
Remove a permission for a specific application
|
||||
|
||||
Keyword argument:
|
||||
app -- an application OR sftp, xmpp (metronome), mail
|
||||
permission -- name of the permission ("main" by default)
|
||||
|
||||
"""
|
||||
|
||||
if permission == "main" and not force:
|
||||
raise YunohostError('remove_main_permission_not_allowed')
|
||||
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
operation_logger.start()
|
||||
if not ldap.remove('cn=%s,ou=permission' % str(permission + '.' + app)):
|
||||
raise YunohostError('permission_deletion_failed', permission=permission, app=app)
|
||||
if sync_perm:
|
||||
permission_sync_to_user()
|
||||
logger.debug(m18n.n('permission_deleted', permission=permission, app=app))
|
||||
|
||||
logger.debug(m18n.n('permission_created', permission=permission))
|
||||
return user_permission_list(full=True)["permissions"][permission]
|
||||
|
||||
|
||||
def permission_sync_to_user(force=False):
|
||||
@is_unit_operation()
|
||||
def permission_url(operation_logger, permission, url=None, sync_perm=True):
|
||||
"""
|
||||
Update urls related to a permission for a specific application
|
||||
|
||||
Keyword argument:
|
||||
permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
|
||||
url -- (optional) URL for which access will be allowed/forbidden
|
||||
"""
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
# Fetch existing permission
|
||||
|
||||
existing_permission = user_permission_list(full=True)["permissions"].get(permission, None)
|
||||
if not existing_permission:
|
||||
raise YunohostError('permission_not_found', permission=permission)
|
||||
|
||||
# Compute new url list
|
||||
old_url = existing_permission["url"]
|
||||
|
||||
if old_url == url:
|
||||
logger.warning(m18n.n('permission_update_nothing_to_do'))
|
||||
return existing_permission
|
||||
|
||||
# Actually commit the change
|
||||
|
||||
operation_logger.related_to.append(('app', permission.split(".")[0]))
|
||||
operation_logger.start()
|
||||
|
||||
try:
|
||||
ldap.update('cn=%s,ou=permission' % permission, {'URL': [url]})
|
||||
except Exception as e:
|
||||
raise YunohostError('permission_update_failed', permission=permission, error=e)
|
||||
|
||||
if sync_perm:
|
||||
permission_sync_to_user()
|
||||
|
||||
logger.debug(m18n.n('permission_updated', permission=permission))
|
||||
return user_permission_list(full=True)["permissions"][permission]
|
||||
|
||||
|
||||
@is_unit_operation()
|
||||
def permission_delete(operation_logger, permission, force=False, sync_perm=True):
|
||||
"""
|
||||
Delete a permission
|
||||
|
||||
Keyword argument:
|
||||
permission -- Name of the permission (e.g. mail or nextcloud or wordpress.editors)
|
||||
"""
|
||||
|
||||
# By default, manipulate main permission
|
||||
if "." not in permission:
|
||||
permission = permission + ".main"
|
||||
|
||||
if permission.endswith(".main") and not force:
|
||||
raise YunohostError('permission_cannot_remove_main')
|
||||
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
# Make sure this permission exists
|
||||
|
||||
existing_permission = user_permission_list(full=True)["permissions"].get(permission, None)
|
||||
if not existing_permission:
|
||||
raise YunohostError('permission_not_found', permission=permission)
|
||||
|
||||
# Actually delete the permission
|
||||
|
||||
operation_logger.related_to.append(('app', permission.split(".")[0]))
|
||||
operation_logger.start()
|
||||
|
||||
try:
|
||||
ldap.remove('cn=%s,ou=permission' % permission)
|
||||
except Exception as e:
|
||||
raise YunohostError('permission_deletion_failed', permission=permission, error=e)
|
||||
|
||||
if sync_perm:
|
||||
permission_sync_to_user()
|
||||
logger.debug(m18n.n('permission_deleted', permission=permission))
|
||||
|
||||
|
||||
def permission_sync_to_user():
|
||||
"""
|
||||
Sychronise the inheritPermission attribut in the permission object from the
|
||||
user<->group link and the group<->permission link
|
||||
|
||||
Keyword argument:
|
||||
force -- Force to recreate all attributes. Used generally with the
|
||||
backup which uses "slapadd" which doesnt' use the memberOf overlay.
|
||||
Note that by removing all value and adding a new time, we force the
|
||||
overlay to update all attributes
|
||||
"""
|
||||
# Note that a LDAP operation with the same value that is in LDAP crash SLAP.
|
||||
# So we need to check before each ldap operation that we really change something in LDAP
|
||||
import os
|
||||
from yunohost.app import app_ssowatconf
|
||||
from yunohost.user import user_group_list
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
permission_attrs = [
|
||||
'cn',
|
||||
'member',
|
||||
]
|
||||
group_info = ldap.search('ou=groups,dc=yunohost,dc=org',
|
||||
'(objectclass=groupOfNamesYnh)', permission_attrs)
|
||||
group_info = {g['cn'][0]: g for g in group_info}
|
||||
groups = user_group_list(full=True)["groups"]
|
||||
permissions = user_permission_list(full=True)["permissions"]
|
||||
|
||||
for per in ldap.search('ou=permission,dc=yunohost,dc=org',
|
||||
'(objectclass=permissionYnh)',
|
||||
['cn', 'inheritPermission', 'groupPermission', 'memberUid']):
|
||||
if 'groupPermission' not in per:
|
||||
for permission_name, permission_infos in permissions.items():
|
||||
|
||||
# These are the users currently allowed because there's an 'inheritPermission' object corresponding to it
|
||||
currently_allowed_users = set(permission_infos["corresponding_users"])
|
||||
|
||||
# These are the users that should be allowed because they are member of a group that is allowed for this permission ...
|
||||
should_be_allowed_users = set([user for group in permission_infos["allowed"] for user in groups[group]["members"]])
|
||||
|
||||
# Note that a LDAP operation with the same value that is in LDAP crash SLAP.
|
||||
# So we need to check before each ldap operation that we really change something in LDAP
|
||||
if currently_allowed_users == should_be_allowed_users:
|
||||
# We're all good, this permission is already correctly synchronized !
|
||||
continue
|
||||
user_permission = set()
|
||||
for group in per['groupPermission']:
|
||||
group = group.split("=")[1].split(",")[0]
|
||||
if 'member' not in group_info[group]:
|
||||
continue
|
||||
for user in group_info[group]['member']:
|
||||
user_permission.add(user)
|
||||
|
||||
if 'inheritPermission' not in per:
|
||||
per['inheritPermission'] = []
|
||||
if 'memberUid' not in per:
|
||||
per['memberUid'] = []
|
||||
new_inherited_perms = {'inheritPermission': ["uid=%s,ou=users,dc=yunohost,dc=org" % u for u in should_be_allowed_users],
|
||||
'memberUid': should_be_allowed_users}
|
||||
|
||||
uid_val = [v.split("=")[1].split(",")[0] for v in user_permission]
|
||||
if user_permission == set(per['inheritPermission']) and set(uid_val) == set(per['memberUid']) and not force:
|
||||
continue
|
||||
inheritPermission = {'inheritPermission': user_permission, 'memberUid': uid_val}
|
||||
if force:
|
||||
if per['groupPermission']:
|
||||
if not ldap.update('cn=%s,ou=permission' % per['cn'][0], {'groupPermission': []}):
|
||||
raise YunohostError('permission_update_failed_clear')
|
||||
if not ldap.update('cn=%s,ou=permission' % per['cn'][0], {'groupPermission': per['groupPermission']}):
|
||||
raise YunohostError('permission_update_failed_populate')
|
||||
if per['inheritPermission']:
|
||||
if not ldap.update('cn=%s,ou=permission' % per['cn'][0], {'inheritPermission': []}):
|
||||
raise YunohostError('permission_update_failed_clear')
|
||||
if user_permission:
|
||||
if not ldap.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission):
|
||||
raise YunohostError('permission_update_failed')
|
||||
else:
|
||||
if not ldap.update('cn=%s,ou=permission' % per['cn'][0], inheritPermission):
|
||||
raise YunohostError('permission_update_failed')
|
||||
logger.debug(m18n.n('permission_generated'))
|
||||
# Commit the change with the new inherited stuff
|
||||
try:
|
||||
ldap.update('cn=%s,ou=permission' % permission_name, new_inherited_perms)
|
||||
except Exception as e:
|
||||
raise YunohostError('permission_update_failed', permission=permission_name, error=e)
|
||||
|
||||
logger.debug("The permission database has been resynchronized")
|
||||
|
||||
app_ssowatconf()
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run
|
|||
or not os.path.exists(REGEN_CONF_FILE)):
|
||||
from yunohost.tools import _get_migration_by_name
|
||||
migration = _get_migration_by_name("decouple_regenconf_from_services")
|
||||
migration.migrate()
|
||||
migration.run()
|
||||
|
||||
result = {}
|
||||
|
||||
|
@ -131,6 +131,16 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run
|
|||
show_info=False)['hooks']
|
||||
names.remove('ssh')
|
||||
|
||||
# Dirty hack for legacy code : avoid attempting to regen the conf for
|
||||
# glances because it got removed ... This is only needed *once*
|
||||
# during the upgrade from 3.7 to 3.8 because Yunohost will attempt to
|
||||
# regen glance's conf *before* it gets automatically removed from
|
||||
# services.yml (which will happens only during the regen-conf of
|
||||
# 'yunohost', so at the very end of the regen-conf cycle) Anyway,
|
||||
# this can be safely removed once we're in >= 4.0
|
||||
if "glances" in names:
|
||||
names.remove("glances")
|
||||
|
||||
pre_result = hook_callback('conf_regen', names, pre_callback=_pre_call)
|
||||
|
||||
# Keep only the hook names with at least one success
|
||||
|
@ -525,31 +535,32 @@ def _process_regen_conf(system_conf, new_conf=None, save=True):
|
|||
|
||||
def manually_modified_files():
|
||||
|
||||
# We do this to have --quiet, i.e. don't throw a whole bunch of logs
|
||||
# just to fetch this...
|
||||
# Might be able to optimize this by looking at what the regen conf does
|
||||
# and only do the part that checks file hashes...
|
||||
cmd = "yunohost tools regen-conf --dry-run --output-as json --quiet"
|
||||
j = json.loads(subprocess.check_output(cmd.split()))
|
||||
|
||||
# j is something like :
|
||||
# {"postfix": {"applied": {}, "pending": {"/etc/postfix/main.cf": {"status": "modified"}}}
|
||||
|
||||
output = []
|
||||
for app, actions in j.items():
|
||||
for action, files in actions.items():
|
||||
for filename, infos in files.items():
|
||||
if infos["status"] == "modified":
|
||||
output.append(filename)
|
||||
regenconf_categories = _get_regenconf_infos()
|
||||
for category, infos in regenconf_categories.items():
|
||||
conffiles = infos["conffiles"]
|
||||
for path, hash_ in conffiles.items():
|
||||
if hash_ != _calculate_hash(path):
|
||||
output.append(path)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def manually_modified_files_compared_to_debian_default():
|
||||
def manually_modified_files_compared_to_debian_default(ignore_handled_by_regenconf=False):
|
||||
|
||||
# from https://serverfault.com/a/90401
|
||||
r = subprocess.check_output("dpkg-query -W -f='${Conffiles}\n' '*' \
|
||||
| awk 'OFS=\" \"{print $2,$1}' \
|
||||
| md5sum -c 2>/dev/null \
|
||||
| awk -F': ' '$2 !~ /OK/{print $1}'", shell=True)
|
||||
return r.strip().split("\n")
|
||||
files = subprocess.check_output("dpkg-query -W -f='${Conffiles}\n' '*' \
|
||||
| awk 'OFS=\" \"{print $2,$1}' \
|
||||
| md5sum -c 2>/dev/null \
|
||||
| awk -F': ' '$2 !~ /OK/{print $1}'", shell=True)
|
||||
files = files.strip().split("\n")
|
||||
|
||||
if ignore_handled_by_regenconf:
|
||||
regenconf_categories = _get_regenconf_infos()
|
||||
regenconf_files = []
|
||||
for infos in regenconf_categories.values():
|
||||
regenconf_files.extend(infos["conffiles"].keys())
|
||||
|
||||
files = [f for f in files if f not in regenconf_files]
|
||||
|
||||
return files
|
||||
|
|
|
@ -1,9 +1,32 @@
|
|||
import pytest
|
||||
import sys
|
||||
import moulinette
|
||||
|
||||
from moulinette import m18n
|
||||
from yunohost.utils.error import YunohostError
|
||||
from contextlib import contextmanager
|
||||
|
||||
sys.path.append("..")
|
||||
|
||||
|
||||
|
||||
@contextmanager
|
||||
def message(mocker, key, **kwargs):
|
||||
mocker.spy(m18n, "n")
|
||||
yield
|
||||
m18n.n.assert_any_call(key, **kwargs)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def raiseYunohostError(mocker, key, **kwargs):
|
||||
with pytest.raises(YunohostError) as e_info:
|
||||
yield
|
||||
assert e_info._excinfo[1].key == key
|
||||
if kwargs:
|
||||
assert e_info._excinfo[1].kwargs == kwargs
|
||||
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--yunodebug", action="store_true", default=False)
|
||||
|
||||
|
@ -41,11 +64,11 @@ def pytest_cmdline_main(config):
|
|||
root_handlers = set(handlers)
|
||||
|
||||
# Define loggers level
|
||||
level = 'INFO'
|
||||
level = 'DEBUG'
|
||||
if config.option.yunodebug:
|
||||
tty_level = 'DEBUG'
|
||||
else:
|
||||
tty_level = 'SUCCESS'
|
||||
tty_level = 'INFO'
|
||||
|
||||
# Custom logging configuration
|
||||
logging = {
|
||||
|
|
344
src/yunohost/tests/test_apps.py
Normal file
344
src/yunohost/tests/test_apps.py
Normal file
|
@ -0,0 +1,344 @@
|
|||
import glob
|
||||
import os
|
||||
import pytest
|
||||
import shutil
|
||||
import requests
|
||||
|
||||
from conftest import message, raiseYunohostError
|
||||
|
||||
from moulinette import m18n
|
||||
from moulinette.utils.filesystem import mkdir
|
||||
|
||||
from yunohost.app import app_install, app_remove, app_ssowatconf, _is_installed, app_upgrade
|
||||
from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list
|
||||
from yunohost.utils.error import YunohostError
|
||||
from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
|
||||
clean()
|
||||
|
||||
def teardown_function(function):
|
||||
|
||||
clean()
|
||||
|
||||
def clean():
|
||||
|
||||
# Make sure we have a ssowat
|
||||
os.system("mkdir -p /etc/ssowat/")
|
||||
app_ssowatconf()
|
||||
|
||||
# Gotta first remove break yo system
|
||||
# because some remaining stuff might
|
||||
# make the other app_remove crashs ;P
|
||||
if _is_installed("break_yo_system"):
|
||||
app_remove("break_yo_system")
|
||||
|
||||
if _is_installed("legacy_app"):
|
||||
app_remove("legacy_app")
|
||||
|
||||
if _is_installed("full_domain_app"):
|
||||
app_remove("full_domain_app")
|
||||
|
||||
to_remove = []
|
||||
to_remove += glob.glob("/etc/nginx/conf.d/*.d/*legacy*")
|
||||
to_remove += glob.glob("/etc/nginx/conf.d/*.d/*full_domain*")
|
||||
to_remove += glob.glob("/etc/nginx/conf.d/*.d/*break_yo_system*")
|
||||
for filepath in to_remove:
|
||||
os.remove(filepath)
|
||||
|
||||
to_remove = []
|
||||
to_remove += glob.glob("/etc/yunohost/apps/*legacy_app*")
|
||||
to_remove += glob.glob("/etc/yunohost/apps/*full_domain_app*")
|
||||
to_remove += glob.glob("/etc/yunohost/apps/*break_yo_system*")
|
||||
to_remove += glob.glob("/var/www/*legacy*")
|
||||
to_remove += glob.glob("/var/www/*full_domain*")
|
||||
for folderpath in to_remove:
|
||||
shutil.rmtree(folderpath, ignore_errors=True)
|
||||
|
||||
os.system("systemctl reset-failed nginx") # Reset failed quota for service to avoid running into start-limit rate ?
|
||||
os.system("systemctl start nginx")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def check_LDAP_db_integrity_call():
|
||||
check_LDAP_db_integrity()
|
||||
yield
|
||||
check_LDAP_db_integrity()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def check_permission_for_apps_call():
|
||||
check_permission_for_apps()
|
||||
yield
|
||||
check_permission_for_apps()
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def secondary_domain(request):
|
||||
|
||||
if "example.test" not in domain_list()["domains"]:
|
||||
domain_add("example.test")
|
||||
|
||||
def remove_example_domain():
|
||||
domain_remove("example.test")
|
||||
request.addfinalizer(remove_example_domain)
|
||||
|
||||
return "example.test"
|
||||
|
||||
|
||||
#
|
||||
# Helpers #
|
||||
#
|
||||
|
||||
def app_expected_files(domain, app):
|
||||
|
||||
yield "/etc/nginx/conf.d/%s.d/%s.conf" % (domain, app)
|
||||
if app.startswith("legacy_app"):
|
||||
yield "/var/www/%s/index.html" % app
|
||||
yield "/etc/yunohost/apps/%s/settings.yml" % app
|
||||
yield "/etc/yunohost/apps/%s/manifest.json" % app
|
||||
yield "/etc/yunohost/apps/%s/scripts/install" % app
|
||||
yield "/etc/yunohost/apps/%s/scripts/remove" % app
|
||||
|
||||
|
||||
def app_is_installed(domain, app):
|
||||
|
||||
return _is_installed(app) and all(os.path.exists(f) for f in app_expected_files(domain, app))
|
||||
|
||||
|
||||
def app_is_not_installed(domain, app):
|
||||
|
||||
return not _is_installed(app) and not all(os.path.exists(f) for f in app_expected_files(domain, app))
|
||||
|
||||
|
||||
def app_is_exposed_on_http(domain, path, message_in_page):
|
||||
|
||||
try:
|
||||
r = requests.get("http://127.0.0.1" + path + "/", headers={"Host": domain}, timeout=10, verify=False)
|
||||
return r.status_code == 200 and message_in_page in r.text
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
|
||||
def install_legacy_app(domain, path, public=True):
|
||||
|
||||
app_install("./tests/apps/legacy_app_ynh",
|
||||
args="domain=%s&path=%s&is_public=%s" % (domain, path, 1 if public else 0),
|
||||
force=True)
|
||||
|
||||
|
||||
def install_full_domain_app(domain):
|
||||
|
||||
app_install("./tests/apps/full_domain_app_ynh",
|
||||
args="domain=%s" % domain,
|
||||
force=True)
|
||||
|
||||
|
||||
def install_break_yo_system(domain, breakwhat):
|
||||
|
||||
app_install("./tests/apps/break_yo_system_ynh",
|
||||
args="domain=%s&breakwhat=%s" % (domain, breakwhat),
|
||||
force=True)
|
||||
|
||||
|
||||
def test_legacy_app_install_main_domain():
|
||||
|
||||
main_domain = _get_maindomain()
|
||||
|
||||
install_legacy_app(main_domain, "/legacy")
|
||||
|
||||
assert app_is_installed(main_domain, "legacy_app")
|
||||
assert app_is_exposed_on_http(main_domain, "/legacy", "This is a dummy app")
|
||||
|
||||
app_remove("legacy_app")
|
||||
|
||||
assert app_is_not_installed(main_domain, "legacy_app")
|
||||
|
||||
|
||||
def test_legacy_app_install_secondary_domain(secondary_domain):
|
||||
|
||||
install_legacy_app(secondary_domain, "/legacy")
|
||||
|
||||
assert app_is_installed(secondary_domain, "legacy_app")
|
||||
assert app_is_exposed_on_http(secondary_domain, "/legacy", "This is a dummy app")
|
||||
|
||||
app_remove("legacy_app")
|
||||
|
||||
assert app_is_not_installed(secondary_domain, "legacy_app")
|
||||
|
||||
|
||||
def test_legacy_app_install_secondary_domain_on_root(secondary_domain):
|
||||
|
||||
install_legacy_app(secondary_domain, "/")
|
||||
|
||||
assert app_is_installed(secondary_domain, "legacy_app")
|
||||
assert app_is_exposed_on_http(secondary_domain, "/", "This is a dummy app")
|
||||
|
||||
app_remove("legacy_app")
|
||||
|
||||
assert app_is_not_installed(secondary_domain, "legacy_app")
|
||||
|
||||
|
||||
def test_legacy_app_install_private(secondary_domain):
|
||||
|
||||
install_legacy_app(secondary_domain, "/legacy", public=False)
|
||||
|
||||
assert app_is_installed(secondary_domain, "legacy_app")
|
||||
assert not app_is_exposed_on_http(secondary_domain, "/legacy", "This is a dummy app")
|
||||
|
||||
app_remove("legacy_app")
|
||||
|
||||
assert app_is_not_installed(secondary_domain, "legacy_app")
|
||||
|
||||
|
||||
def test_legacy_app_install_unknown_domain(mocker):
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
with message(mocker, "app_argument_invalid"):
|
||||
install_legacy_app("whatever.nope", "/legacy")
|
||||
|
||||
assert app_is_not_installed("whatever.nope", "legacy_app")
|
||||
|
||||
|
||||
def test_legacy_app_install_multiple_instances(secondary_domain):
|
||||
|
||||
install_legacy_app(secondary_domain, "/foo")
|
||||
install_legacy_app(secondary_domain, "/bar")
|
||||
|
||||
assert app_is_installed(secondary_domain, "legacy_app")
|
||||
assert app_is_exposed_on_http(secondary_domain, "/foo", "This is a dummy app")
|
||||
|
||||
assert app_is_installed(secondary_domain, "legacy_app__2")
|
||||
assert app_is_exposed_on_http(secondary_domain, "/bar", "This is a dummy app")
|
||||
|
||||
app_remove("legacy_app")
|
||||
|
||||
assert app_is_not_installed(secondary_domain, "legacy_app")
|
||||
assert app_is_installed(secondary_domain, "legacy_app__2")
|
||||
|
||||
app_remove("legacy_app__2")
|
||||
|
||||
assert app_is_not_installed(secondary_domain, "legacy_app")
|
||||
assert app_is_not_installed(secondary_domain, "legacy_app__2")
|
||||
|
||||
|
||||
def test_legacy_app_install_path_unavailable(mocker, secondary_domain):
|
||||
|
||||
# These will be removed in teardown
|
||||
install_legacy_app(secondary_domain, "/legacy")
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
with message(mocker, "app_location_unavailable"):
|
||||
install_legacy_app(secondary_domain, "/")
|
||||
|
||||
assert app_is_installed(secondary_domain, "legacy_app")
|
||||
assert app_is_not_installed(secondary_domain, "legacy_app__2")
|
||||
|
||||
|
||||
def test_legacy_app_install_with_nginx_down(mocker, secondary_domain):
|
||||
|
||||
os.system("systemctl stop nginx")
|
||||
|
||||
with raiseYunohostError(mocker, "app_action_cannot_be_ran_because_required_services_down"):
|
||||
install_legacy_app(secondary_domain, "/legacy")
|
||||
|
||||
|
||||
def test_legacy_app_failed_install(mocker, secondary_domain):
|
||||
|
||||
# This will conflict with the folder that the app
|
||||
# attempts to create, making the install fail
|
||||
mkdir("/var/www/legacy_app/", 0o750)
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
with message(mocker, 'app_install_script_failed'):
|
||||
install_legacy_app(secondary_domain, "/legacy")
|
||||
|
||||
assert app_is_not_installed(secondary_domain, "legacy_app")
|
||||
|
||||
|
||||
def test_legacy_app_failed_remove(mocker, secondary_domain):
|
||||
|
||||
install_legacy_app(secondary_domain, "/legacy")
|
||||
|
||||
# The remove script runs with set -eu and attempt to remove this
|
||||
# file without -f, so will fail if it's not there ;)
|
||||
os.remove("/etc/nginx/conf.d/%s.d/%s.conf" % (secondary_domain, "legacy_app"))
|
||||
|
||||
# TODO / FIXME : can't easily validate that 'app_not_properly_removed'
|
||||
# is triggered for weird reasons ...
|
||||
app_remove("legacy_app")
|
||||
|
||||
#
|
||||
# Well here, we hit the classical issue where if an app removal script
|
||||
# fails, so far there's no obvious way to make sure that all files related
|
||||
# to this app got removed ...
|
||||
#
|
||||
assert app_is_not_installed(secondary_domain, "legacy")
|
||||
|
||||
|
||||
def test_full_domain_app(secondary_domain):
|
||||
|
||||
install_full_domain_app(secondary_domain)
|
||||
|
||||
assert app_is_exposed_on_http(secondary_domain, "/", "This is a dummy app")
|
||||
|
||||
|
||||
def test_full_domain_app_with_conflicts(mocker, secondary_domain):
|
||||
|
||||
install_legacy_app(secondary_domain, "/legacy")
|
||||
|
||||
with raiseYunohostError(mocker, "app_full_domain_unavailable"):
|
||||
install_full_domain_app(secondary_domain)
|
||||
|
||||
|
||||
def test_systemfuckedup_during_app_install(mocker, secondary_domain):
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
with message(mocker, "app_install_failed"):
|
||||
with message(mocker, 'app_action_broke_system'):
|
||||
install_break_yo_system(secondary_domain, breakwhat="install")
|
||||
|
||||
assert app_is_not_installed(secondary_domain, "break_yo_system")
|
||||
|
||||
|
||||
def test_systemfuckedup_during_app_remove(mocker, secondary_domain):
|
||||
|
||||
install_break_yo_system(secondary_domain, breakwhat="remove")
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
with message(mocker, 'app_action_broke_system'):
|
||||
with message(mocker, 'app_removed'):
|
||||
app_remove("break_yo_system")
|
||||
|
||||
assert app_is_not_installed(secondary_domain, "break_yo_system")
|
||||
|
||||
|
||||
def test_systemfuckedup_during_app_install_and_remove(mocker, secondary_domain):
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
with message(mocker, "app_install_failed"):
|
||||
with message(mocker, 'app_action_broke_system'):
|
||||
install_break_yo_system(secondary_domain, breakwhat="everything")
|
||||
|
||||
assert app_is_not_installed(secondary_domain, "break_yo_system")
|
||||
|
||||
|
||||
def test_systemfuckedup_during_app_upgrade(mocker, secondary_domain):
|
||||
|
||||
install_break_yo_system(secondary_domain, breakwhat="upgrade")
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
with message(mocker, 'app_action_broke_system'):
|
||||
app_upgrade("break_yo_system", file="./tests/apps/break_yo_system_ynh")
|
||||
|
||||
|
||||
def test_failed_multiple_app_upgrade(mocker, secondary_domain):
|
||||
|
||||
install_legacy_app(secondary_domain, "/legacy")
|
||||
install_break_yo_system(secondary_domain, breakwhat="upgrade")
|
||||
|
||||
with raiseYunohostError(mocker, 'app_not_upgraded'):
|
||||
app_upgrade(["break_yo_system", "legacy_app"],
|
||||
file={"break_yo_system": "./tests/apps/break_yo_system_ynh",
|
||||
"legacy": "./tests/apps/legacy_app_ynh"})
|
|
@ -2,15 +2,14 @@ import pytest
|
|||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
from mock import ANY
|
||||
|
||||
from moulinette import m18n
|
||||
from conftest import message, raiseYunohostError
|
||||
|
||||
from yunohost.app import app_install, app_remove, app_ssowatconf
|
||||
from yunohost.app import _is_installed
|
||||
from yunohost.backup import backup_create, backup_restore, backup_list, backup_info, backup_delete, _recursive_umount
|
||||
from yunohost.domain import _get_maindomain
|
||||
from yunohost.utils.error import YunohostError
|
||||
from yunohost.user import user_permission_list
|
||||
from yunohost.user import user_permission_list, user_create, user_list, user_delete
|
||||
from yunohost.tests.test_permission import check_LDAP_db_integrity, check_permission_for_apps
|
||||
|
||||
# Get main domain
|
||||
|
@ -38,10 +37,10 @@ def setup_function(function):
|
|||
add_archive_wordpress_from_2p4()
|
||||
assert len(backup_list()["archives"]) == 1
|
||||
|
||||
if "with_backup_legacy_app_installed" in markers:
|
||||
assert not app_is_installed("backup_legacy_app")
|
||||
install_app("backup_legacy_app_ynh", "/yolo")
|
||||
assert app_is_installed("backup_legacy_app")
|
||||
if "with_legacy_app_installed" in markers:
|
||||
assert not app_is_installed("legacy_app")
|
||||
install_app("legacy_app_ynh", "/yolo")
|
||||
assert app_is_installed("legacy_app")
|
||||
|
||||
if "with_backup_recommended_app_installed" in markers:
|
||||
assert not app_is_installed("backup_recommended_app")
|
||||
|
@ -59,6 +58,13 @@ def setup_function(function):
|
|||
add_archive_system_from_2p4()
|
||||
assert len(backup_list()["archives"]) == 1
|
||||
|
||||
if "with_permission_app_installed" in markers:
|
||||
assert not app_is_installed("permissions_app")
|
||||
user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh")
|
||||
install_app("permissions_app_ynh", "/urlpermissionapp"
|
||||
"&admin=alice")
|
||||
assert app_is_installed("permissions_app")
|
||||
|
||||
|
||||
def teardown_function(function):
|
||||
|
||||
|
@ -73,6 +79,9 @@ def teardown_function(function):
|
|||
if "clean_opt_dir" in markers:
|
||||
shutil.rmtree("/opt/test_backup_output_directory")
|
||||
|
||||
if "alice" in user_list()["users"]:
|
||||
user_delete("alice")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def check_LDAP_db_integrity_call():
|
||||
|
@ -92,6 +101,9 @@ def check_permission_for_apps_call():
|
|||
|
||||
def app_is_installed(app):
|
||||
|
||||
if app == "permissions_app":
|
||||
return _is_installed(app)
|
||||
|
||||
# These are files we know should be installed by the app
|
||||
app_files = []
|
||||
app_files.append("/etc/nginx/conf.d/%s.d/%s.conf" % (maindomain, app))
|
||||
|
@ -105,7 +117,7 @@ def backup_test_dependencies_are_met():
|
|||
|
||||
# Dummy test apps (or backup archives)
|
||||
assert os.path.exists("./tests/apps/backup_wordpress_from_2p4")
|
||||
assert os.path.exists("./tests/apps/backup_legacy_app_ynh")
|
||||
assert os.path.exists("./tests/apps/legacy_app_ynh")
|
||||
assert os.path.exists("./tests/apps/backup_recommended_app_ynh")
|
||||
|
||||
return True
|
||||
|
@ -155,14 +167,9 @@ def delete_all_backups():
|
|||
|
||||
def uninstall_test_apps_if_needed():
|
||||
|
||||
if _is_installed("backup_legacy_app"):
|
||||
app_remove("backup_legacy_app")
|
||||
|
||||
if _is_installed("backup_recommended_app"):
|
||||
app_remove("backup_recommended_app")
|
||||
|
||||
if _is_installed("wordpress"):
|
||||
app_remove("wordpress")
|
||||
for app in ["legacy_app", "backup_recommended_app", "wordpress", "permissions_app"]:
|
||||
if _is_installed(app):
|
||||
app_remove(app)
|
||||
|
||||
|
||||
def install_app(app, path, additionnal_args=""):
|
||||
|
@ -198,10 +205,11 @@ def add_archive_system_from_2p4():
|
|||
#
|
||||
|
||||
|
||||
def test_backup_only_ldap():
|
||||
def test_backup_only_ldap(mocker):
|
||||
|
||||
# Create the backup
|
||||
backup_create(system=["conf_ldap"], apps=None)
|
||||
with message(mocker, "backup_created"):
|
||||
backup_create(system=["conf_ldap"], apps=None)
|
||||
|
||||
archives = backup_list()["archives"]
|
||||
assert len(archives) == 1
|
||||
|
@ -214,24 +222,22 @@ def test_backup_only_ldap():
|
|||
|
||||
def test_backup_system_part_that_does_not_exists(mocker):
|
||||
|
||||
mocker.spy(m18n, "n")
|
||||
|
||||
# Create the backup
|
||||
with pytest.raises(YunohostError):
|
||||
backup_create(system=["yolol"], apps=None)
|
||||
with message(mocker, 'backup_hook_unknown', hook="doesnt_exist"):
|
||||
with raiseYunohostError(mocker, "backup_nothings_done"):
|
||||
backup_create(system=["doesnt_exist"], apps=None)
|
||||
|
||||
m18n.n.assert_any_call('backup_hook_unknown', hook="yolol")
|
||||
m18n.n.assert_any_call('backup_nothings_done')
|
||||
|
||||
#
|
||||
# System backup and restore #
|
||||
#
|
||||
|
||||
|
||||
def test_backup_and_restore_all_sys():
|
||||
def test_backup_and_restore_all_sys(mocker):
|
||||
|
||||
# Create the backup
|
||||
backup_create(system=[], apps=None)
|
||||
with message(mocker, "backup_created"):
|
||||
backup_create(system=[], apps=None)
|
||||
|
||||
archives = backup_list()["archives"]
|
||||
assert len(archives) == 1
|
||||
|
@ -247,8 +253,9 @@ def test_backup_and_restore_all_sys():
|
|||
assert not os.path.exists("/etc/ssowat/conf.json")
|
||||
|
||||
# Restore the backup
|
||||
backup_restore(name=archives[0], force=True,
|
||||
system=[], apps=None)
|
||||
with message(mocker, "restore_complete"):
|
||||
backup_restore(name=archives[0], force=True,
|
||||
system=[], apps=None)
|
||||
|
||||
# Check ssowat conf is back
|
||||
assert os.path.exists("/etc/ssowat/conf.json")
|
||||
|
@ -262,16 +269,18 @@ def test_backup_and_restore_all_sys():
|
|||
def test_restore_system_from_Ynh2p4(monkeypatch, mocker):
|
||||
|
||||
# Backup current system
|
||||
backup_create(system=[], apps=None)
|
||||
with message(mocker, "backup_created"):
|
||||
backup_create(system=[], apps=None)
|
||||
archives = backup_list()["archives"]
|
||||
assert len(archives) == 2
|
||||
|
||||
# Restore system archive from 2.4
|
||||
try:
|
||||
backup_restore(name=backup_list()["archives"][1],
|
||||
system=[],
|
||||
apps=None,
|
||||
force=True)
|
||||
with message(mocker, "restore_complete"):
|
||||
backup_restore(name=backup_list()["archives"][1],
|
||||
system=[],
|
||||
apps=None,
|
||||
force=True)
|
||||
finally:
|
||||
# Restore system as it was
|
||||
backup_restore(name=backup_list()["archives"][0],
|
||||
|
@ -298,12 +307,10 @@ def test_backup_script_failure_handling(monkeypatch, mocker):
|
|||
# call with monkeypatch). We also patch m18n to check later it's been called
|
||||
# with the expected error message key
|
||||
monkeypatch.setattr("yunohost.backup.hook_exec", custom_hook_exec)
|
||||
mocker.spy(m18n, "n")
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
backup_create(system=None, apps=["backup_recommended_app"])
|
||||
|
||||
m18n.n.assert_any_call('backup_app_failed', app='backup_recommended_app')
|
||||
with message(mocker, 'backup_app_failed', app='backup_recommended_app'):
|
||||
with raiseYunohostError(mocker, 'backup_nothings_done'):
|
||||
backup_create(system=None, apps=["backup_recommended_app"])
|
||||
|
||||
|
||||
@pytest.mark.with_backup_recommended_app_installed
|
||||
|
@ -319,25 +326,17 @@ def test_backup_not_enough_free_space(monkeypatch, mocker):
|
|||
monkeypatch.setattr("yunohost.backup.free_space_in_directory",
|
||||
custom_free_space_in_directory)
|
||||
|
||||
mocker.spy(m18n, "n")
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
with raiseYunohostError(mocker, 'not_enough_disk_space'):
|
||||
backup_create(system=None, apps=["backup_recommended_app"])
|
||||
|
||||
m18n.n.assert_any_call('not_enough_disk_space', path=ANY)
|
||||
|
||||
|
||||
def test_backup_app_not_installed(mocker):
|
||||
|
||||
assert not _is_installed("wordpress")
|
||||
|
||||
mocker.spy(m18n, "n")
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
backup_create(system=None, apps=["wordpress"])
|
||||
|
||||
m18n.n.assert_any_call("unbackup_app", app="wordpress")
|
||||
m18n.n.assert_any_call('backup_nothings_done')
|
||||
with message(mocker, "unbackup_app", app="wordpress"):
|
||||
with raiseYunohostError(mocker, 'backup_nothings_done'):
|
||||
backup_create(system=None, apps=["wordpress"])
|
||||
|
||||
|
||||
@pytest.mark.with_backup_recommended_app_installed
|
||||
|
@ -347,13 +346,9 @@ def test_backup_app_with_no_backup_script(mocker):
|
|||
os.system("rm %s" % backup_script)
|
||||
assert not os.path.exists(backup_script)
|
||||
|
||||
mocker.spy(m18n, "n")
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
backup_create(system=None, apps=["backup_recommended_app"])
|
||||
|
||||
m18n.n.assert_any_call("backup_with_no_backup_script_for_app", app="backup_recommended_app")
|
||||
m18n.n.assert_any_call('backup_nothings_done')
|
||||
with message(mocker, "backup_with_no_backup_script_for_app", app="backup_recommended_app"):
|
||||
with raiseYunohostError(mocker, 'backup_nothings_done'):
|
||||
backup_create(system=None, apps=["backup_recommended_app"])
|
||||
|
||||
|
||||
@pytest.mark.with_backup_recommended_app_installed
|
||||
|
@ -363,23 +358,21 @@ def test_backup_app_with_no_restore_script(mocker):
|
|||
os.system("rm %s" % restore_script)
|
||||
assert not os.path.exists(restore_script)
|
||||
|
||||
mocker.spy(m18n, "n")
|
||||
|
||||
# Backuping an app with no restore script will only display a warning to the
|
||||
# user...
|
||||
|
||||
backup_create(system=None, apps=["backup_recommended_app"])
|
||||
|
||||
m18n.n.assert_any_call("backup_with_no_restore_script_for_app", app="backup_recommended_app")
|
||||
with message(mocker, "backup_with_no_restore_script_for_app", app="backup_recommended_app"):
|
||||
backup_create(system=None, apps=["backup_recommended_app"])
|
||||
|
||||
|
||||
@pytest.mark.clean_opt_dir
|
||||
def test_backup_with_different_output_directory():
|
||||
def test_backup_with_different_output_directory(mocker):
|
||||
|
||||
# Create the backup
|
||||
backup_create(system=["conf_ssh"], apps=None,
|
||||
output_directory="/opt/test_backup_output_directory",
|
||||
name="backup")
|
||||
with message(mocker, "backup_created"):
|
||||
backup_create(system=["conf_ssh"], apps=None,
|
||||
output_directory="/opt/test_backup_output_directory",
|
||||
name="backup")
|
||||
|
||||
assert os.path.exists("/opt/test_backup_output_directory/backup.tar.gz")
|
||||
|
||||
|
@ -393,12 +386,14 @@ def test_backup_with_different_output_directory():
|
|||
|
||||
|
||||
@pytest.mark.clean_opt_dir
|
||||
def test_backup_with_no_compress():
|
||||
def test_backup_with_no_compress(mocker):
|
||||
|
||||
# Create the backup
|
||||
backup_create(system=["conf_nginx"], apps=None,
|
||||
output_directory="/opt/test_backup_output_directory",
|
||||
no_compress=True,
|
||||
name="backup")
|
||||
with message(mocker, "backup_created"):
|
||||
backup_create(system=["conf_nginx"], apps=None,
|
||||
output_directory="/opt/test_backup_output_directory",
|
||||
no_compress=True,
|
||||
name="backup")
|
||||
|
||||
assert os.path.exists("/opt/test_backup_output_directory/info.json")
|
||||
|
||||
|
@ -408,10 +403,11 @@ def test_backup_with_no_compress():
|
|||
#
|
||||
|
||||
@pytest.mark.with_wordpress_archive_from_2p4
|
||||
def test_restore_app_wordpress_from_Ynh2p4():
|
||||
def test_restore_app_wordpress_from_Ynh2p4(mocker):
|
||||
|
||||
backup_restore(system=None, name=backup_list()["archives"][0],
|
||||
apps=["wordpress"])
|
||||
with message(mocker, "restore_complete"):
|
||||
backup_restore(system=None, name=backup_list()["archives"][0],
|
||||
apps=["wordpress"])
|
||||
|
||||
|
||||
@pytest.mark.with_wordpress_archive_from_2p4
|
||||
|
@ -423,16 +419,14 @@ def test_restore_app_script_failure_handling(monkeypatch, mocker):
|
|||
raise Exception
|
||||
|
||||
monkeypatch.setattr("yunohost.backup.hook_exec", custom_hook_exec)
|
||||
mocker.spy(m18n, "n")
|
||||
|
||||
assert not _is_installed("wordpress")
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
backup_restore(system=None, name=backup_list()["archives"][0],
|
||||
apps=["wordpress"])
|
||||
with message(mocker, 'restore_app_failed', app='wordpress'):
|
||||
with raiseYunohostError(mocker, 'restore_nothings_done'):
|
||||
backup_restore(system=None, name=backup_list()["archives"][0],
|
||||
apps=["wordpress"])
|
||||
|
||||
m18n.n.assert_any_call('restore_app_failed', app='wordpress')
|
||||
m18n.n.assert_any_call('restore_nothings_done')
|
||||
assert not _is_installed("wordpress")
|
||||
|
||||
|
||||
|
@ -444,18 +438,13 @@ def test_restore_app_not_enough_free_space(monkeypatch, mocker):
|
|||
|
||||
monkeypatch.setattr("yunohost.backup.free_space_in_directory",
|
||||
custom_free_space_in_directory)
|
||||
mocker.spy(m18n, "n")
|
||||
|
||||
assert not _is_installed("wordpress")
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
with raiseYunohostError(mocker, 'restore_not_enough_disk_space'):
|
||||
backup_restore(system=None, name=backup_list()["archives"][0],
|
||||
apps=["wordpress"])
|
||||
|
||||
m18n.n.assert_any_call('restore_not_enough_disk_space',
|
||||
free_space=0,
|
||||
margin=ANY,
|
||||
needed_space=ANY)
|
||||
assert not _is_installed("wordpress")
|
||||
|
||||
|
||||
|
@ -465,13 +454,11 @@ def test_restore_app_not_in_backup(mocker):
|
|||
assert not _is_installed("wordpress")
|
||||
assert not _is_installed("yoloswag")
|
||||
|
||||
mocker.spy(m18n, "n")
|
||||
with message(mocker, 'backup_archive_app_not_found', app="yoloswag"):
|
||||
with raiseYunohostError(mocker, 'restore_nothings_done'):
|
||||
backup_restore(system=None, name=backup_list()["archives"][0],
|
||||
apps=["yoloswag"])
|
||||
|
||||
with pytest.raises(YunohostError):
|
||||
backup_restore(system=None, name=backup_list()["archives"][0],
|
||||
apps=["yoloswag"])
|
||||
|
||||
m18n.n.assert_any_call('backup_archive_app_not_found', app="yoloswag")
|
||||
assert not _is_installed("wordpress")
|
||||
assert not _is_installed("yoloswag")
|
||||
|
||||
|
@ -481,44 +468,72 @@ def test_restore_app_already_installed(mocker):
|
|||
|
||||
assert not _is_installed("wordpress")
|
||||
|
||||
backup_restore(system=None, name=backup_list()["archives"][0],
|
||||
apps=["wordpress"])
|
||||
|
||||
assert _is_installed("wordpress")
|
||||
|
||||
mocker.spy(m18n, "n")
|
||||
with pytest.raises(YunohostError):
|
||||
with message(mocker, "restore_complete"):
|
||||
backup_restore(system=None, name=backup_list()["archives"][0],
|
||||
apps=["wordpress"])
|
||||
|
||||
m18n.n.assert_any_call('restore_already_installed_app', app="wordpress")
|
||||
m18n.n.assert_any_call('restore_nothings_done')
|
||||
assert _is_installed("wordpress")
|
||||
|
||||
with message(mocker, 'restore_already_installed_app', app="wordpress"):
|
||||
with raiseYunohostError(mocker, 'restore_nothings_done'):
|
||||
backup_restore(system=None, name=backup_list()["archives"][0],
|
||||
apps=["wordpress"])
|
||||
|
||||
assert _is_installed("wordpress")
|
||||
|
||||
|
||||
@pytest.mark.with_backup_legacy_app_installed
|
||||
def test_backup_and_restore_legacy_app():
|
||||
@pytest.mark.with_legacy_app_installed
|
||||
def test_backup_and_restore_legacy_app(mocker):
|
||||
|
||||
_test_backup_and_restore_app("backup_legacy_app")
|
||||
_test_backup_and_restore_app(mocker, "legacy_app")
|
||||
|
||||
|
||||
@pytest.mark.with_backup_recommended_app_installed
|
||||
def test_backup_and_restore_recommended_app():
|
||||
def test_backup_and_restore_recommended_app(mocker):
|
||||
|
||||
_test_backup_and_restore_app("backup_recommended_app")
|
||||
_test_backup_and_restore_app(mocker, "backup_recommended_app")
|
||||
|
||||
|
||||
@pytest.mark.with_backup_recommended_app_installed_with_ynh_restore
|
||||
def test_backup_and_restore_with_ynh_restore():
|
||||
def test_backup_and_restore_with_ynh_restore(mocker):
|
||||
|
||||
_test_backup_and_restore_app("backup_recommended_app")
|
||||
_test_backup_and_restore_app(mocker, "backup_recommended_app")
|
||||
|
||||
@pytest.mark.with_permission_app_installed
|
||||
def test_backup_and_restore_permission_app():
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert "permissions_app.main" in res
|
||||
assert "permissions_app.admin" in res
|
||||
assert "permissions_app.dev" in res
|
||||
assert res['permissions_app.main']['url'] == "/"
|
||||
assert res['permissions_app.admin']['url'] == "/admin"
|
||||
assert res['permissions_app.dev']['url'] == "/dev"
|
||||
|
||||
assert res['permissions_app.main']['allowed'] == ["visitors"]
|
||||
assert res['permissions_app.admin']['allowed'] == ["alice"]
|
||||
assert res['permissions_app.dev']['allowed'] == []
|
||||
|
||||
_test_backup_and_restore_app("permissions_app")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert "permissions_app.main" in res
|
||||
assert "permissions_app.admin" in res
|
||||
assert "permissions_app.dev" in res
|
||||
assert res['permissions_app.main']['url'] == "/"
|
||||
assert res['permissions_app.admin']['url'] == "/admin"
|
||||
assert res['permissions_app.dev']['url'] == "/dev"
|
||||
|
||||
assert res['permissions_app.main']['allowed'] == ["visitors"]
|
||||
assert res['permissions_app.admin']['allowed'] == ["alice"]
|
||||
assert res['permissions_app.dev']['allowed'] == []
|
||||
|
||||
|
||||
def _test_backup_and_restore_app(app):
|
||||
def _test_backup_and_restore_app(mocker, app):
|
||||
|
||||
# Create a backup of this app
|
||||
backup_create(system=None, apps=[app])
|
||||
with message(mocker, "backup_created"):
|
||||
backup_create(system=None, apps=[app])
|
||||
|
||||
archives = backup_list()["archives"]
|
||||
assert len(archives) == 1
|
||||
|
@ -531,18 +546,18 @@ def _test_backup_and_restore_app(app):
|
|||
# Uninstall the app
|
||||
app_remove(app)
|
||||
assert not app_is_installed(app)
|
||||
assert app not in user_permission_list()['permissions']
|
||||
assert app+".main" not in user_permission_list()['permissions']
|
||||
|
||||
# Restore the app
|
||||
backup_restore(system=None, name=archives[0],
|
||||
apps=[app])
|
||||
with message(mocker, "restore_complete"):
|
||||
backup_restore(system=None, name=archives[0],
|
||||
apps=[app])
|
||||
|
||||
assert app_is_installed(app)
|
||||
|
||||
# Check permission
|
||||
per_list = user_permission_list()['permissions']
|
||||
assert app in per_list
|
||||
assert "main" in per_list[app]
|
||||
assert app+".main" in per_list
|
||||
|
||||
#
|
||||
# Some edge cases #
|
||||
|
@ -557,13 +572,11 @@ def test_restore_archive_with_no_json(mocker):
|
|||
|
||||
assert "badbackup" in backup_list()["archives"]
|
||||
|
||||
mocker.spy(m18n, "n")
|
||||
with pytest.raises(YunohostError):
|
||||
with raiseYunohostError(mocker, 'backup_invalid_archive'):
|
||||
backup_restore(name="badbackup", force=True)
|
||||
m18n.n.assert_any_call('backup_invalid_archive')
|
||||
|
||||
|
||||
def test_backup_binds_are_readonly(monkeypatch):
|
||||
def test_backup_binds_are_readonly(mocker, monkeypatch):
|
||||
|
||||
def custom_mount_and_backup(self, backup_manager):
|
||||
self.manager = backup_manager
|
||||
|
@ -584,4 +597,5 @@ def test_backup_binds_are_readonly(monkeypatch):
|
|||
custom_mount_and_backup)
|
||||
|
||||
# Create the backup
|
||||
backup_create(system=[])
|
||||
with message(mocker, "backup_created"):
|
||||
backup_create(system=[])
|
||||
|
|
|
@ -1,37 +1,42 @@
|
|||
import requests
|
||||
import pytest
|
||||
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.app import app_install, app_remove, app_change_url, app_list
|
||||
from yunohost.user import user_list, user_create, user_permission_list, user_delete, user_group_list, user_group_delete, user_permission_add, user_permission_remove, user_permission_clear
|
||||
from yunohost.permission import permission_add, permission_update, permission_remove
|
||||
from conftest import message, raiseYunohostError
|
||||
|
||||
from yunohost.app import app_install, app_remove, app_change_url, app_list, app_map
|
||||
from yunohost.user import user_list, user_create, user_delete, \
|
||||
user_group_list, user_group_delete
|
||||
from yunohost.permission import user_permission_update, user_permission_list, user_permission_reset, \
|
||||
permission_create, permission_delete, permission_url
|
||||
from yunohost.domain import _get_maindomain
|
||||
from yunohost.utils.error import YunohostError
|
||||
|
||||
# Get main domain
|
||||
maindomain = _get_maindomain()
|
||||
dummy_password = "test123Ynh"
|
||||
|
||||
|
||||
def clean_user_groups_permission():
|
||||
for u in user_list()['users']:
|
||||
user_delete(u)
|
||||
|
||||
for g in user_group_list()['groups']:
|
||||
if g != "all_users":
|
||||
if g not in ["all_users", "visitors"]:
|
||||
user_group_delete(g)
|
||||
|
||||
for a, per in user_permission_list()['permissions'].items():
|
||||
if a in ['wiki', 'blog', 'site']:
|
||||
for p in per:
|
||||
permission_remove(a, p, force=True, sync_perm=False)
|
||||
for p in user_permission_list()['permissions']:
|
||||
if any(p.startswith(name) for name in ["wiki", "blog", "site", "permissions_app"]):
|
||||
permission_delete(p, force=True, sync_perm=False)
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
clean_user_groups_permission()
|
||||
|
||||
user_create("alice", "Alice", "White", "alice@" + maindomain, "test123Ynh")
|
||||
user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh")
|
||||
permission_add("wiki", "main", [maindomain + "/wiki"], sync_perm=False)
|
||||
permission_add("blog", "main", sync_perm=False)
|
||||
user_create("alice", "Alice", "White", "alice@" + maindomain, dummy_password)
|
||||
user_create("bob", "Bob", "Snow", "bob@" + maindomain, dummy_password)
|
||||
permission_create("wiki.main", url="/", sync_perm=False)
|
||||
permission_create("blog.main", sync_perm=False)
|
||||
user_permission_update("blog.main", remove="all_users", add="alice")
|
||||
|
||||
user_permission_add(["blog"], "main", group="alice")
|
||||
|
||||
def teardown_function(function):
|
||||
clean_user_groups_permission()
|
||||
|
@ -39,6 +44,11 @@ def teardown_function(function):
|
|||
app_remove("permissions_app")
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
app_remove("legacy_app")
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def check_LDAP_db_integrity_call():
|
||||
|
@ -46,6 +56,7 @@ def check_LDAP_db_integrity_call():
|
|||
yield
|
||||
check_LDAP_db_integrity()
|
||||
|
||||
|
||||
def check_LDAP_db_integrity():
|
||||
# Here we check that all attributes in all object are sychronized.
|
||||
# Here is the list of attributes per object:
|
||||
|
@ -57,7 +68,7 @@ def check_LDAP_db_integrity():
|
|||
# One part should be done automatically by the "memberOf" overlay of LDAP.
|
||||
# The other part is done by the the "permission_sync_to_user" function of the permission module
|
||||
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
user_search = ldap.search('ou=users,dc=yunohost,dc=org',
|
||||
|
@ -76,161 +87,197 @@ def check_LDAP_db_integrity():
|
|||
|
||||
for user in user_search:
|
||||
user_dn = 'uid=' + user['uid'][0] + ',ou=users,dc=yunohost,dc=org'
|
||||
group_list = [m.split("=")[1].split(",")[0] for m in user['memberOf']]
|
||||
permission_list = []
|
||||
if 'permission' in user:
|
||||
permission_list = [m.split("=")[1].split(",")[0] for m in user['permission']]
|
||||
group_list = [_ldap_path_extract(m, "cn") for m in user['memberOf']]
|
||||
permission_list = [_ldap_path_extract(m, "cn") for m in user.get('permission', [])]
|
||||
|
||||
# This user's DN sould be found in all groups it is a member of
|
||||
for group in group_list:
|
||||
assert user_dn in group_map[group]['member']
|
||||
|
||||
# This user's DN should be found in all perms it has access to
|
||||
for permission in permission_list:
|
||||
assert user_dn in permission_map[permission]['inheritPermission']
|
||||
|
||||
for permission in permission_search:
|
||||
permission_dn = 'cn=' + permission['cn'][0] + ',ou=permission,dc=yunohost,dc=org'
|
||||
user_list = []
|
||||
group_list = []
|
||||
if 'inheritPermission' in permission:
|
||||
user_list = [m.split("=")[1].split(",")[0] for m in permission['inheritPermission']]
|
||||
assert set(user_list) == set(permission['memberUid'])
|
||||
if 'groupPermission' in permission:
|
||||
group_list = [m.split("=")[1].split(",")[0] for m in permission['groupPermission']]
|
||||
|
||||
# inheritPermission uid's should match memberUids
|
||||
user_list = [_ldap_path_extract(m, "uid") for m in permission.get('inheritPermission', [])]
|
||||
assert set(user_list) == set(permission.get('memberUid', []))
|
||||
|
||||
# This perm's DN should be found on all related users it is related to
|
||||
for user in user_list:
|
||||
assert permission_dn in user_map[user]['permission']
|
||||
|
||||
# Same for groups : we should find the permission's DN for all related groups
|
||||
group_list = [_ldap_path_extract(m, "cn") for m in permission.get('groupPermission', [])]
|
||||
for group in group_list:
|
||||
assert permission_dn in group_map[group]['permission']
|
||||
if 'member' in group_map[group]:
|
||||
user_list_in_group = [m.split("=")[1].split(",")[0] for m in group_map[group]['member']]
|
||||
assert set(user_list_in_group) <= set(user_list)
|
||||
|
||||
# The list of user in the group should be a subset of all users related to the current permission
|
||||
users_in_group = [_ldap_path_extract(m, "uid") for m in group_map[group].get("member", [])]
|
||||
assert set(users_in_group) <= set(user_list)
|
||||
|
||||
for group in group_search:
|
||||
group_dn = 'cn=' + group['cn'][0] + ',ou=groups,dc=yunohost,dc=org'
|
||||
user_list = []
|
||||
permission_list = []
|
||||
if 'member' in group:
|
||||
user_list = [m.split("=")[1].split(",")[0] for m in group['member']]
|
||||
if group['cn'][0] in user_list:
|
||||
# If it's the main group of the user it's normal that it is not in the memberUid
|
||||
g_list = list(user_list)
|
||||
g_list.remove(group['cn'][0])
|
||||
if 'memberUid' in group:
|
||||
assert set(g_list) == set(group['memberUid'])
|
||||
else:
|
||||
assert g_list == []
|
||||
else:
|
||||
assert set(user_list) == set(group['memberUid'])
|
||||
if 'permission' in group:
|
||||
permission_list = [m.split("=")[1].split(",")[0] for m in group['permission']]
|
||||
|
||||
user_list = [_ldap_path_extract(m, "uid") for m in group.get("member", [])]
|
||||
# For primary groups, we should find that :
|
||||
# - len(user_list) is 1 (a primary group has only 1 member)
|
||||
# - the group name should be an existing yunohost user
|
||||
# - memberUid is empty (meaning no other member than the corresponding user)
|
||||
if group['cn'][0] in user_list:
|
||||
assert len(user_list) == 1
|
||||
assert group["cn"][0] in user_map
|
||||
assert group.get('memberUid', []) == []
|
||||
# Otherwise, user_list and memberUid should have the same content
|
||||
else:
|
||||
assert set(user_list) == set(group.get('memberUid', []))
|
||||
|
||||
# For all users members, this group should be in the "memberOf" on the other side
|
||||
for user in user_list:
|
||||
assert group_dn in user_map[user]['memberOf']
|
||||
|
||||
# For all the permissions of this group, the group should be among the "groupPermission" on the other side
|
||||
permission_list = [_ldap_path_extract(m, "cn") for m in group.get('permission', [])]
|
||||
for permission in permission_list:
|
||||
assert group_dn in permission_map[permission]['groupPermission']
|
||||
if 'inheritPermission' in permission_map:
|
||||
allowed_user_list = [m.split("=")[1].split(",")[0] for m in permission_map[permission]['inheritPermission']]
|
||||
assert set(user_list) <= set(allowed_user_list)
|
||||
|
||||
# And the list of user of this group (user_list) should be a subset of all allowed users for this perm...
|
||||
allowed_user_list = [_ldap_path_extract(m, "uid") for m in permission_map[permission].get('inheritPermission', [])]
|
||||
assert set(user_list) <= set(allowed_user_list)
|
||||
|
||||
|
||||
def check_permission_for_apps():
|
||||
# We check that the for each installed apps we have at last the "main" permission
|
||||
# and we don't have any permission linked to no apps. The only exception who is not liked to an app
|
||||
# is mail, metronome, and sftp
|
||||
# is mail, xmpp, and sftp
|
||||
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
ldap = _get_ldap_interface()
|
||||
permission_search = ldap.search('ou=permission,dc=yunohost,dc=org',
|
||||
'(objectclass=permissionYnh)',
|
||||
['cn', 'groupPermission', 'inheritPermission', 'memberUid'])
|
||||
app_perms = user_permission_list(ignore_system_perms=True)["permissions"].keys()
|
||||
|
||||
# Keep only the prefix so that
|
||||
# ["foo.main", "foo.pwet", "bar.main"]
|
||||
# becomes
|
||||
# {"bar", "foo"}
|
||||
# and compare this to the list of installed apps ...
|
||||
|
||||
app_perms_prefix = set(p.split(".")[0] for p in app_perms)
|
||||
|
||||
installed_apps = {app['id'] for app in app_list(installed=True)['apps']}
|
||||
permission_list_set = {permission['cn'][0].split(".")[1] for permission in permission_search}
|
||||
|
||||
extra_service_permission = set(['mail', 'metronome'])
|
||||
if 'sftp' in permission_list_set:
|
||||
extra_service_permission.add('sftp')
|
||||
assert installed_apps == permission_list_set - extra_service_permission
|
||||
assert installed_apps == app_perms_prefix
|
||||
|
||||
|
||||
def can_access_webpage(webpath, logged_as=None):
|
||||
|
||||
webpath = webpath.rstrip("/")
|
||||
sso_url = "https://" + maindomain + "/yunohost/sso/"
|
||||
|
||||
# Anonymous access
|
||||
if not logged_as:
|
||||
r = requests.get(webpath, verify=False)
|
||||
# Login as a user using dummy password
|
||||
else:
|
||||
with requests.Session() as session:
|
||||
session.post(sso_url,
|
||||
data={"user": logged_as,
|
||||
"password": dummy_password},
|
||||
headers={"Referer": sso_url,
|
||||
"Content-Type": "application/x-www-form-urlencoded"},
|
||||
verify=False)
|
||||
# We should have some cookies related to authentication now
|
||||
assert session.cookies
|
||||
r = session.get(webpath, verify=False)
|
||||
|
||||
# If we can't access it, we got redirected to the SSO
|
||||
return not r.url.startswith(sso_url)
|
||||
|
||||
|
||||
#
|
||||
# List functions
|
||||
#
|
||||
|
||||
def test_list_permission():
|
||||
res = user_permission_list()['permissions']
|
||||
def test_permission_list():
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
|
||||
assert "wiki" in res
|
||||
assert "main" in res['wiki']
|
||||
assert "blog" in res
|
||||
assert "main" in res['blog']
|
||||
assert "mail" in res
|
||||
assert "main" in res['mail']
|
||||
assert "metronome" in res
|
||||
assert "main" in res['metronome']
|
||||
assert ["all_users"] == res['wiki']['main']['allowed_groups']
|
||||
assert ["alice"] == res['blog']['main']['allowed_groups']
|
||||
assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users'])
|
||||
assert ["alice"] == res['blog']['main']['allowed_users']
|
||||
assert [maindomain + "/wiki"] == res['wiki']['main']['URL']
|
||||
assert "wiki.main" in res
|
||||
assert "blog.main" in res
|
||||
assert "mail.main" in res
|
||||
assert "xmpp.main" in res
|
||||
assert res['wiki.main']['allowed'] == ["all_users"]
|
||||
assert res['blog.main']['allowed'] == ["alice"]
|
||||
assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"])
|
||||
assert res['blog.main']['corresponding_users'] == ["alice"]
|
||||
assert res['wiki.main']['url'] == "/"
|
||||
|
||||
#
|
||||
# Create - Remove functions
|
||||
#
|
||||
|
||||
def test_add_permission_1():
|
||||
permission_add("site", "test")
|
||||
|
||||
def test_permission_create_main(mocker):
|
||||
with message(mocker, "permission_created", permission="site.main"):
|
||||
permission_create("site.main")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert "site.main" in res
|
||||
assert res['site.main']['allowed'] == ["all_users"]
|
||||
assert set(res['site.main']['corresponding_users']) == set(["alice", "bob"])
|
||||
|
||||
|
||||
def test_permission_create_extra(mocker):
|
||||
with message(mocker, "permission_created", permission="site.test"):
|
||||
permission_create("site.test")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert "site.test" in res
|
||||
# all_users is only enabled by default on .main perms
|
||||
assert "all_users" not in res['site.test']['allowed']
|
||||
assert res['site.test']['corresponding_users'] == []
|
||||
|
||||
|
||||
def test_permission_create_with_allowed():
|
||||
permission_create("site.test", allowed=["alice"])
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert "site.test" in res
|
||||
assert res['site.test']['allowed'] == ["alice"]
|
||||
|
||||
|
||||
def test_permission_delete(mocker):
|
||||
with message(mocker, "permission_deleted", permission="wiki.main"):
|
||||
permission_delete("wiki.main", force=True)
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert "site" in res
|
||||
assert "test" in res['site']
|
||||
assert "all_users" in res['site']['test']['allowed_groups']
|
||||
assert set(["alice", "bob"]) == set(res['site']['test']['allowed_users'])
|
||||
|
||||
def test_add_permission_2():
|
||||
permission_add("site", "main", default_allow=False)
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert "site" in res
|
||||
assert "main" in res['site']
|
||||
assert [] == res['site']['main']['allowed_groups']
|
||||
assert [] == res['site']['main']['allowed_users']
|
||||
|
||||
def test_remove_permission():
|
||||
permission_remove("wiki", "main", force=True)
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert "wiki" not in res
|
||||
assert "wiki.main" not in res
|
||||
|
||||
#
|
||||
# Error on create - remove function
|
||||
#
|
||||
|
||||
def test_add_bad_permission():
|
||||
# Create permission with same name
|
||||
with pytest.raises(YunohostError):
|
||||
permission_add("wiki", "main")
|
||||
|
||||
def test_remove_bad_permission():
|
||||
# Remove not existant permission
|
||||
with pytest.raises(MoulinetteError):
|
||||
permission_remove("non_exit", "main", force=True)
|
||||
def test_permission_create_already_existing(mocker):
|
||||
with raiseYunohostError(mocker, "permission_already_exist"):
|
||||
permission_create("wiki.main")
|
||||
|
||||
|
||||
def test_permission_delete_doesnt_existing(mocker):
|
||||
with raiseYunohostError(mocker, "permission_not_found"):
|
||||
permission_delete("doesnt.exist", force=True)
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert "wiki" in res
|
||||
assert "main" in res['wiki']
|
||||
assert "blog" in res
|
||||
assert "main" in res['blog']
|
||||
assert "mail" in res
|
||||
assert "main" in res ['mail']
|
||||
assert "metronome" in res
|
||||
assert "main" in res['metronome']
|
||||
assert "wiki.main" in res
|
||||
assert "blog.main" in res
|
||||
assert "mail.main" in res
|
||||
assert "xmpp.main" in res
|
||||
|
||||
def test_remove_main_permission():
|
||||
with pytest.raises(YunohostError):
|
||||
permission_remove("blog", "main")
|
||||
|
||||
def test_permission_delete_main_without_force(mocker):
|
||||
with raiseYunohostError(mocker, "permission_cannot_remove_main"):
|
||||
permission_delete("blog.main")
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert "mail" in res
|
||||
assert "main" in res['mail']
|
||||
assert "blog.main" in res
|
||||
|
||||
#
|
||||
# Update functions
|
||||
|
@ -238,182 +285,222 @@ def test_remove_main_permission():
|
|||
|
||||
# user side functions
|
||||
|
||||
def test_allow_first_group():
|
||||
# Remove permission to all_users and define per users
|
||||
user_permission_add(["wiki"], "main", group="alice")
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert ['alice'] == res['wiki']['main']['allowed_users']
|
||||
assert ['alice'] == res['wiki']['main']['allowed_groups']
|
||||
def test_permission_add_group(mocker):
|
||||
with message(mocker, "permission_updated", permission="wiki.main"):
|
||||
user_permission_update("wiki.main", add="alice")
|
||||
|
||||
def test_allow_other_group():
|
||||
# Allow new user in a permission
|
||||
user_permission_add(["blog"], "main", group="bob")
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert set(res['wiki.main']['allowed']) == set(["all_users", "alice"])
|
||||
assert set(res['wiki.main']['corresponding_users']) == set(["alice", "bob"])
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users'])
|
||||
assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_groups'])
|
||||
|
||||
def test_disallow_group_1():
|
||||
# Disallow a user in a permission
|
||||
user_permission_remove(["blog"], "main", group="alice")
|
||||
def test_permission_remove_group(mocker):
|
||||
with message(mocker, "permission_updated", permission="blog.main"):
|
||||
user_permission_update("blog.main", remove="alice")
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert [] == res['blog']['main']['allowed_users']
|
||||
assert [] == res['blog']['main']['allowed_groups']
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['blog.main']['allowed'] == []
|
||||
assert res['blog.main']['corresponding_users'] == []
|
||||
|
||||
def test_allow_group_1():
|
||||
# Allow a user when he is already allowed
|
||||
user_permission_add(["blog"], "main", group="alice")
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert ["alice"] == res['blog']['main']['allowed_users']
|
||||
assert ["alice"] == res['blog']['main']['allowed_groups']
|
||||
def test_permission_add_and_remove_group(mocker):
|
||||
with message(mocker, "permission_updated", permission="wiki.main"):
|
||||
user_permission_update("wiki.main", add="alice", remove="all_users")
|
||||
|
||||
def test_disallow_group_1():
|
||||
# Disallow a user when he is already disallowed
|
||||
user_permission_remove(["blog"], "main", group="bob")
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['wiki.main']['allowed'] == ["alice"]
|
||||
assert res['wiki.main']['corresponding_users'] == ["alice"]
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert ["alice"] == res['blog']['main']['allowed_users']
|
||||
assert ["alice"] == res['blog']['main']['allowed_groups']
|
||||
|
||||
def test_reset_permission():
|
||||
def test_permission_add_group_already_allowed(mocker):
|
||||
with message(mocker, "permission_already_allowed", permission="blog.main", group="alice"):
|
||||
user_permission_update("blog.main", add="alice")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['blog.main']['allowed'] == ["alice"]
|
||||
assert res['blog.main']['corresponding_users'] == ["alice"]
|
||||
|
||||
|
||||
def test_permission_remove_group_already_not_allowed(mocker):
|
||||
with message(mocker, "permission_already_disallowed", permission="blog.main", group="bob"):
|
||||
user_permission_update("blog.main", remove="bob")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['blog.main']['allowed'] == ["alice"]
|
||||
assert res['blog.main']['corresponding_users'] == ["alice"]
|
||||
|
||||
|
||||
def test_permission_reset(mocker):
|
||||
with message(mocker, "permission_updated", permission="blog.main"):
|
||||
user_permission_reset("blog.main")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['blog.main']['allowed'] == ["all_users"]
|
||||
assert set(res['blog.main']['corresponding_users']) == set(["alice", "bob"])
|
||||
|
||||
|
||||
def test_permission_reset_idempotency():
|
||||
# Reset permission
|
||||
user_permission_clear(["blog"], "main")
|
||||
user_permission_reset("blog.main")
|
||||
user_permission_reset("blog.main")
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert set(["alice", "bob"]) == set(res['blog']['main']['allowed_users'])
|
||||
assert ["all_users"] == res['blog']['main']['allowed_groups']
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['blog.main']['allowed'] == ["all_users"]
|
||||
assert set(res['blog.main']['corresponding_users']) == set(["alice", "bob"])
|
||||
|
||||
# internal functions
|
||||
|
||||
def test_add_url_1():
|
||||
# Add URL in permission which hasn't any URL defined
|
||||
permission_update("blog", "main", add_url=[maindomain + "/testA"])
|
||||
def test_permission_reset_idempotency():
|
||||
# Reset permission
|
||||
user_permission_reset("blog.main")
|
||||
user_permission_reset("blog.main")
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert [maindomain + "/testA"] == res['blog']['main']['URL']
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['blog.main']['allowed'] == ["all_users"]
|
||||
assert set(res['blog.main']['corresponding_users']) == set(["alice", "bob"])
|
||||
|
||||
def test_add_url_2():
|
||||
# Add a second URL in a permission
|
||||
permission_update("wiki", "main", add_url=[maindomain + "/testA"])
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert set([maindomain + "/testA", maindomain + "/wiki"]) == set(res['wiki']['main']['URL'])
|
||||
|
||||
def test_remove_url_1():
|
||||
permission_update("wiki", "main", remove_url=[maindomain + "/wiki"])
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert 'URL' not in res['wiki']['main']
|
||||
|
||||
def test_add_url_3():
|
||||
# Add a url already added
|
||||
permission_update("wiki", "main", add_url=[maindomain + "/wiki"])
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert [maindomain + "/wiki"] == res['wiki']['main']['URL']
|
||||
|
||||
def test_remove_url_2():
|
||||
# Remove a url not added (with a permission which contain some URL)
|
||||
permission_update("wiki", "main", remove_url=[maindomain + "/not_exist"])
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert [maindomain + "/wiki"] == res['wiki']['main']['URL']
|
||||
|
||||
def test_remove_url_2():
|
||||
# Remove a url not added (with a permission which contain no URL)
|
||||
permission_update("blog", "main", remove_url=[maindomain + "/not_exist"])
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert 'URL' not in res['blog']['main']
|
||||
|
||||
#
|
||||
# Error on update function
|
||||
#
|
||||
|
||||
def test_disallow_bad_group_1():
|
||||
# Disallow a group when the group all_users is allowed
|
||||
with pytest.raises(YunohostError):
|
||||
user_permission_remove("wiki", "main", group="alice")
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert ["all_users"] == res['wiki']['main']['allowed_groups']
|
||||
assert set(["alice", "bob"]) == set(res['wiki']['main']['allowed_users'])
|
||||
def test_permission_add_group_that_doesnt_exist(mocker):
|
||||
with raiseYunohostError(mocker, "group_unknown"):
|
||||
user_permission_update("blog.main", add="doesnt_exist")
|
||||
|
||||
def test_allow_bad_user():
|
||||
# Allow a non existant group
|
||||
with pytest.raises(YunohostError):
|
||||
user_permission_add(["blog"], "main", group="not_exist")
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['blog.main']['allowed'] == ["alice"]
|
||||
assert res['blog.main']['corresponding_users'] == ["alice"]
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert ["alice"] == res['blog']['main']['allowed_groups']
|
||||
assert ["alice"] == res['blog']['main']['allowed_users']
|
||||
|
||||
def test_disallow_bad_group_2():
|
||||
# Disallow a non existant group
|
||||
with pytest.raises(YunohostError):
|
||||
user_permission_remove(["blog"], "main", group="not_exist")
|
||||
def test_permission_update_permission_that_doesnt_exist(mocker):
|
||||
with raiseYunohostError(mocker, "permission_not_found"):
|
||||
user_permission_update("doesnt.exist", add="alice")
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert ["alice"] == res['blog']['main']['allowed_groups']
|
||||
assert ["alice"] == res['blog']['main']['allowed_users']
|
||||
|
||||
def test_allow_bad_permission_1():
|
||||
# Allow a user to a non existant permission
|
||||
with pytest.raises(YunohostError):
|
||||
user_permission_add(["wiki"], "not_exit", group="alice")
|
||||
# Permission url management
|
||||
|
||||
def test_allow_bad_permission_2():
|
||||
# Allow a user to a non existant permission
|
||||
with pytest.raises(YunohostError):
|
||||
user_permission_add(["not_exit"], "main", group="alice")
|
||||
def test_permission_redefine_url():
|
||||
permission_url("blog.main", url="/pwet")
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res["blog.main"]["url"] == "/pwet"
|
||||
|
||||
def test_permission_remove_url():
|
||||
permission_url("blog.main", url=None)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res["blog.main"]["url"] is None
|
||||
|
||||
#
|
||||
# Application interaction
|
||||
#
|
||||
|
||||
def test_install_app():
|
||||
|
||||
def test_permission_app_install():
|
||||
app_install("./tests/apps/permissions_app_ynh",
|
||||
args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
|
||||
args="domain=%s&path=%s&is_public=0&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert "permissions_app" in res
|
||||
assert "main" in res['permissions_app']
|
||||
assert [maindomain + "/urlpermissionapp"] == res['permissions_app']['main']['URL']
|
||||
assert [maindomain + "/urlpermissionapp/admin"] == res['permissions_app']['admin']['URL']
|
||||
assert [maindomain + "/urlpermissionapp/dev"] == res['permissions_app']['dev']['URL']
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert "permissions_app.main" in res
|
||||
assert "permissions_app.admin" in res
|
||||
assert "permissions_app.dev" in res
|
||||
assert res['permissions_app.main']['url'] == "/"
|
||||
assert res['permissions_app.admin']['url'] == "/admin"
|
||||
assert res['permissions_app.dev']['url'] == "/dev"
|
||||
|
||||
assert ["all_users"] == res['permissions_app']['main']['allowed_groups']
|
||||
assert set(["alice", "bob"]) == set(res['permissions_app']['main']['allowed_users'])
|
||||
assert res['permissions_app.main']['allowed'] == ["all_users"]
|
||||
assert set(res['permissions_app.main']['corresponding_users']) == set(["alice", "bob"])
|
||||
|
||||
assert ["alice"] == res['permissions_app']['admin']['allowed_groups']
|
||||
assert ["alice"] == res['permissions_app']['admin']['allowed_users']
|
||||
assert res['permissions_app.admin']['allowed'] == ["alice"]
|
||||
assert res['permissions_app.admin']['corresponding_users'] == ["alice"]
|
||||
|
||||
assert ["all_users"] == res['permissions_app']['dev']['allowed_groups']
|
||||
assert set(["alice", "bob"]) == set(res['permissions_app']['dev']['allowed_users'])
|
||||
assert res['permissions_app.dev']['allowed'] == []
|
||||
assert set(res['permissions_app.dev']['corresponding_users']) == set()
|
||||
|
||||
def test_remove_app():
|
||||
# Check that we get the right stuff in app_map, which is used to generate the ssowatconf
|
||||
assert maindomain + "/urlpermissionapp" in app_map(user="alice").keys()
|
||||
user_permission_update("permissions_app.main", remove="all_users", add="bob")
|
||||
assert maindomain + "/urlpermissionapp" not in app_map(user="alice").keys()
|
||||
assert maindomain + "/urlpermissionapp" in app_map(user="bob").keys()
|
||||
|
||||
|
||||
def test_permission_app_remove():
|
||||
app_install("./tests/apps/permissions_app_ynh",
|
||||
args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
|
||||
args="domain=%s&path=%s&is_public=0&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
|
||||
app_remove("permissions_app")
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert "permissions_app" not in res
|
||||
# Check all permissions for this app got deleted
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert not any(p.startswith("permissions_app.") for p in res.keys())
|
||||
|
||||
def test_change_url():
|
||||
|
||||
def test_permission_app_change_url():
|
||||
app_install("./tests/apps/permissions_app_ynh",
|
||||
args="domain=%s&path=%s&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert [maindomain + "/urlpermissionapp"] == res['permissions_app']['main']['URL']
|
||||
assert [maindomain + "/urlpermissionapp/admin"] == res['permissions_app']['admin']['URL']
|
||||
assert [maindomain + "/urlpermissionapp/dev"] == res['permissions_app']['dev']['URL']
|
||||
# FIXME : should rework this test to look for differences in the generated app map / app tiles ...
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['permissions_app.main']['url'] == "/"
|
||||
assert res['permissions_app.admin']['url'] == "/admin"
|
||||
assert res['permissions_app.dev']['url'] == "/dev"
|
||||
|
||||
app_change_url("permissions_app", maindomain, "/newchangeurl")
|
||||
|
||||
res = user_permission_list()['permissions']
|
||||
assert [maindomain + "/newchangeurl"] == res['permissions_app']['main']['URL']
|
||||
assert [maindomain + "/newchangeurl/admin"] == res['permissions_app']['admin']['URL']
|
||||
assert [maindomain + "/newchangeurl/dev"] == res['permissions_app']['dev']['URL']
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['permissions_app.main']['url'] == "/"
|
||||
assert res['permissions_app.admin']['url'] == "/admin"
|
||||
assert res['permissions_app.dev']['url'] == "/dev"
|
||||
|
||||
|
||||
def test_permission_app_propagation_on_ssowat():
|
||||
|
||||
app_install("./tests/apps/permissions_app_ynh",
|
||||
args="domain=%s&path=%s&is_public=1&admin=%s" % (maindomain, "/urlpermissionapp", "alice"), force=True)
|
||||
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['permissions_app.main']['allowed'] == ["visitors"]
|
||||
|
||||
app_webroot = "https://%s/urlpermissionapp" % maindomain
|
||||
assert can_access_webpage(app_webroot, logged_as=None)
|
||||
assert can_access_webpage(app_webroot, logged_as="alice")
|
||||
|
||||
user_permission_update("permissions_app.main", remove="visitors", add="bob")
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
|
||||
assert not can_access_webpage(app_webroot, logged_as=None)
|
||||
assert not can_access_webpage(app_webroot, logged_as="alice")
|
||||
assert can_access_webpage(app_webroot, logged_as="bob")
|
||||
|
||||
# Test admin access, as configured during install, only alice should be able to access it
|
||||
|
||||
# alice gotta be allowed on the main permission to access the admin tho
|
||||
user_permission_update("permissions_app.main", remove="bob", add="all_users")
|
||||
|
||||
assert not can_access_webpage(app_webroot+"/admin", logged_as=None)
|
||||
assert can_access_webpage(app_webroot+"/admin", logged_as="alice")
|
||||
assert not can_access_webpage(app_webroot+"/admin", logged_as="bob")
|
||||
|
||||
|
||||
def test_permission_legacy_app_propagation_on_ssowat():
|
||||
|
||||
app_install("./tests/apps/legacy_app_ynh",
|
||||
args="domain=%s&path=%s" % (maindomain, "/legacy"), force=True)
|
||||
|
||||
# App is configured as public by default using the legacy unprotected_uri mechanics
|
||||
# It should automatically be migrated during the install
|
||||
res = user_permission_list(full=True)['permissions']
|
||||
assert res['legacy_app.main']['allowed'] == ["visitors"]
|
||||
|
||||
app_webroot = "https://%s/legacy" % maindomain
|
||||
|
||||
assert can_access_webpage(app_webroot, logged_as=None)
|
||||
assert can_access_webpage(app_webroot, logged_as="alice")
|
||||
|
||||
# Try to update the permission and check that permissions are still consistent
|
||||
user_permission_update("legacy_app.main", remove="visitors", add="bob")
|
||||
|
||||
assert not can_access_webpage(app_webroot, logged_as=None)
|
||||
assert not can_access_webpage(app_webroot, logged_as="alice")
|
||||
assert can_access_webpage(app_webroot, logged_as="bob")
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
import pytest
|
||||
|
||||
from moulinette.core import MoulinetteError
|
||||
from yunohost.user import user_list, user_info, user_group_list, user_create, user_delete, user_update, user_group_add, user_group_delete, user_group_update, user_group_info
|
||||
from conftest import message, raiseYunohostError
|
||||
|
||||
from yunohost.user import user_list, user_info, user_create, user_delete, user_update, \
|
||||
user_group_list, user_group_create, user_group_delete, user_group_update
|
||||
from yunohost.domain import _get_maindomain
|
||||
from yunohost.utils.error import YunohostError
|
||||
from yunohost.tests.test_permission import check_LDAP_db_integrity
|
||||
|
||||
# Get main domain
|
||||
maindomain = _get_maindomain()
|
||||
|
||||
|
||||
def clean_user_groups():
|
||||
for u in user_list()['users']:
|
||||
user_delete(u)
|
||||
|
||||
for g in user_group_list()['groups']:
|
||||
if g != "all_users":
|
||||
if g not in ["all_users", "visitors"]:
|
||||
user_group_delete(g)
|
||||
|
||||
|
||||
def setup_function(function):
|
||||
clean_user_groups()
|
||||
|
||||
|
@ -24,14 +27,16 @@ def setup_function(function):
|
|||
user_create("bob", "Bob", "Snow", "bob@" + maindomain, "test123Ynh")
|
||||
user_create("jack", "Jack", "Black", "jack@" + maindomain, "test123Ynh")
|
||||
|
||||
user_group_add("dev")
|
||||
user_group_add("apps")
|
||||
user_group_update("dev", add_user=["alice"])
|
||||
user_group_update("apps", add_user=["bob"])
|
||||
user_group_create("dev")
|
||||
user_group_create("apps")
|
||||
user_group_update("dev", add=["alice"])
|
||||
user_group_update("apps", add=["bob"])
|
||||
|
||||
|
||||
def teardown_function(function):
|
||||
clean_user_groups()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def check_LDAP_db_integrity_call():
|
||||
check_LDAP_db_integrity()
|
||||
|
@ -42,6 +47,7 @@ def check_LDAP_db_integrity_call():
|
|||
# List functions
|
||||
#
|
||||
|
||||
|
||||
def test_list_users():
|
||||
res = user_list()['users']
|
||||
|
||||
|
@ -49,6 +55,7 @@ def test_list_users():
|
|||
assert "bob" in res
|
||||
assert "jack" in res
|
||||
|
||||
|
||||
def test_list_groups():
|
||||
res = user_group_list()['groups']
|
||||
|
||||
|
@ -65,8 +72,11 @@ def test_list_groups():
|
|||
# Create - Remove functions
|
||||
#
|
||||
|
||||
def test_create_user():
|
||||
user_create("albert", "Albert", "Good", "alber@" + maindomain, "test123Ynh")
|
||||
|
||||
def test_create_user(mocker):
|
||||
|
||||
with message(mocker, "user_created"):
|
||||
user_create("albert", "Albert", "Good", "alber@" + maindomain, "test123Ynh")
|
||||
|
||||
group_res = user_group_list()['groups']
|
||||
assert "albert" in user_list()['users']
|
||||
|
@ -74,23 +84,33 @@ def test_create_user():
|
|||
assert "albert" in group_res['albert']['members']
|
||||
assert "albert" in group_res['all_users']['members']
|
||||
|
||||
def test_del_user():
|
||||
user_delete("alice")
|
||||
|
||||
def test_del_user(mocker):
|
||||
|
||||
with message(mocker, "user_deleted"):
|
||||
user_delete("alice")
|
||||
|
||||
group_res = user_group_list()['groups']
|
||||
assert "alice" not in user_list()
|
||||
assert "alice" not in group_res
|
||||
assert "alice" not in group_res['all_users']['members']
|
||||
|
||||
def test_add_group():
|
||||
user_group_add("adminsys")
|
||||
|
||||
def test_create_group(mocker):
|
||||
|
||||
with message(mocker, "group_created", group="adminsys"):
|
||||
user_group_create("adminsys")
|
||||
|
||||
group_res = user_group_list()['groups']
|
||||
assert "adminsys" in group_res
|
||||
assert "members" not in group_res['adminsys']
|
||||
assert "members" in group_res['adminsys'].keys()
|
||||
assert group_res["adminsys"]["members"] == []
|
||||
|
||||
def test_del_group():
|
||||
user_group_delete("dev")
|
||||
|
||||
def test_del_group(mocker):
|
||||
|
||||
with message(mocker, "group_deleted", group="dev"):
|
||||
user_group_delete("dev")
|
||||
|
||||
group_res = user_group_list()['groups']
|
||||
assert "dev" not in group_res
|
||||
|
@ -99,112 +119,129 @@ def test_del_group():
|
|||
# Error on create / remove function
|
||||
#
|
||||
|
||||
def test_add_bad_user_1():
|
||||
# Check email already exist
|
||||
with pytest.raises(MoulinetteError):
|
||||
|
||||
def test_create_user_with_mail_address_already_taken(mocker):
|
||||
with raiseYunohostError(mocker, "user_creation_failed"):
|
||||
user_create("alice2", "Alice", "White", "alice@" + maindomain, "test123Ynh")
|
||||
|
||||
def test_add_bad_user_2():
|
||||
# Check to short password
|
||||
with pytest.raises(MoulinetteError):
|
||||
|
||||
def test_create_user_with_password_too_simple(mocker):
|
||||
with raiseYunohostError(mocker, "password_listed"):
|
||||
user_create("other", "Alice", "White", "other@" + maindomain, "12")
|
||||
|
||||
def test_add_bad_user_3():
|
||||
# Check user already exist
|
||||
with pytest.raises(MoulinetteError):
|
||||
|
||||
def test_create_user_already_exists(mocker):
|
||||
with raiseYunohostError(mocker, "user_already_exists"):
|
||||
user_create("alice", "Alice", "White", "other@" + maindomain, "test123Ynh")
|
||||
|
||||
def test_del_bad_user_1():
|
||||
# Check user not found
|
||||
with pytest.raises(MoulinetteError):
|
||||
user_delete("not_exit")
|
||||
|
||||
def test_add_bad_group_1():
|
||||
def test_update_user_with_mail_address_already_taken(mocker):
|
||||
with raiseYunohostError(mocker, "user_update_failed"):
|
||||
user_update("bob", add_mailalias="alice@" + maindomain)
|
||||
|
||||
|
||||
def test_del_user_that_does_not_exist(mocker):
|
||||
with raiseYunohostError(mocker, "user_unknown"):
|
||||
user_delete("doesnt_exist")
|
||||
|
||||
|
||||
def test_create_group_all_users(mocker):
|
||||
# Check groups already exist with special group "all_users"
|
||||
with pytest.raises(YunohostError):
|
||||
user_group_add("all_users")
|
||||
with raiseYunohostError(mocker, "group_already_exist"):
|
||||
user_group_create("all_users")
|
||||
|
||||
def test_add_bad_group_2():
|
||||
# Check groups already exist (for standard groups)
|
||||
with pytest.raises(MoulinetteError):
|
||||
user_group_add("dev")
|
||||
|
||||
def test_del_bad_group_1():
|
||||
# Check not allowed to remove this groups
|
||||
with pytest.raises(YunohostError):
|
||||
def test_create_group_already_exists(mocker):
|
||||
# Check groups already exist (regular groups)
|
||||
with raiseYunohostError(mocker, "group_already_exist"):
|
||||
user_group_create("dev")
|
||||
|
||||
|
||||
def test_del_group_all_users(mocker):
|
||||
with raiseYunohostError(mocker, "group_cannot_be_deleted"):
|
||||
user_group_delete("all_users")
|
||||
|
||||
def test_del_bad_group_2():
|
||||
# Check groups not found
|
||||
with pytest.raises(MoulinetteError):
|
||||
user_group_delete("not_exit")
|
||||
|
||||
def test_del_group_that_does_not_exist(mocker):
|
||||
with raiseYunohostError(mocker, "group_unknown"):
|
||||
user_group_delete("doesnt_exist")
|
||||
|
||||
#
|
||||
# Update function
|
||||
#
|
||||
|
||||
def test_update_user_1():
|
||||
user_update("alice", firstname="NewName", lastname="NewLast")
|
||||
|
||||
def test_update_user(mocker):
|
||||
with message(mocker, "user_updated"):
|
||||
user_update("alice", firstname="NewName", lastname="NewLast")
|
||||
|
||||
info = user_info("alice")
|
||||
assert "NewName" == info['firstname']
|
||||
assert "NewLast" == info['lastname']
|
||||
assert info['firstname'] == "NewName"
|
||||
assert info['lastname'] == "NewLast"
|
||||
|
||||
def test_update_group_1():
|
||||
user_group_update("dev", add_user=["bob"])
|
||||
|
||||
def test_update_group_add_user(mocker):
|
||||
with message(mocker, "group_updated", group="dev"):
|
||||
user_group_update("dev", add=["bob"])
|
||||
|
||||
group_res = user_group_list()['groups']
|
||||
assert set(["alice", "bob"]) == set(group_res['dev']['members'])
|
||||
assert set(group_res['dev']['members']) == set(["alice", "bob"])
|
||||
|
||||
def test_update_group_2():
|
||||
# Try to add a user in a group when the user is already in
|
||||
user_group_update("apps", add_user=["bob"])
|
||||
|
||||
def test_update_group_add_user_already_in(mocker):
|
||||
with message(mocker, "group_user_already_in_group", user="bob", group="apps"):
|
||||
user_group_update("apps", add=["bob"])
|
||||
|
||||
group_res = user_group_list()['groups']
|
||||
assert ["bob"] == group_res['apps']['members']
|
||||
assert group_res['apps']['members'] == ["bob"]
|
||||
|
||||
def test_update_group_3():
|
||||
# Try to remove a user in a group
|
||||
user_group_update("apps", remove_user=["bob"])
|
||||
|
||||
def test_update_group_remove_user(mocker):
|
||||
with message(mocker, "group_updated", group="apps"):
|
||||
user_group_update("apps", remove=["bob"])
|
||||
|
||||
group_res = user_group_list()['groups']
|
||||
assert "members" not in group_res['apps']
|
||||
assert group_res['apps']['members'] == []
|
||||
|
||||
def test_update_group_4():
|
||||
# Try to remove a user in a group when it is not already in
|
||||
user_group_update("apps", remove_user=["jack"])
|
||||
|
||||
def test_update_group_remove_user_not_already_in(mocker):
|
||||
with message(mocker, "group_user_not_in_group", user="jack", group="apps"):
|
||||
user_group_update("apps", remove=["jack"])
|
||||
|
||||
group_res = user_group_list()['groups']
|
||||
assert ["bob"] == group_res['apps']['members']
|
||||
assert group_res['apps']['members'] == ["bob"]
|
||||
|
||||
#
|
||||
# Error on update functions
|
||||
#
|
||||
|
||||
def test_bad_update_user_1():
|
||||
# Check user not found
|
||||
with pytest.raises(YunohostError):
|
||||
user_update("not_exit", firstname="NewName", lastname="NewLast")
|
||||
|
||||
def test_update_user_that_doesnt_exist(mocker):
|
||||
with raiseYunohostError(mocker, "user_unknown"):
|
||||
user_update("doesnt_exist", firstname="NewName", lastname="NewLast")
|
||||
|
||||
|
||||
def bad_update_group_1():
|
||||
# Check groups not found
|
||||
with pytest.raises(YunohostError):
|
||||
user_group_update("not_exit", add_user=["alice"])
|
||||
def test_update_group_that_doesnt_exist(mocker):
|
||||
with raiseYunohostError(mocker, "group_unknown"):
|
||||
user_group_update("doesnt_exist", add=["alice"])
|
||||
|
||||
def test_bad_update_group_2():
|
||||
# Check remove user in groups "all_users" not allowed
|
||||
with pytest.raises(YunohostError):
|
||||
user_group_update("all_users", remove_user=["alice"])
|
||||
|
||||
def test_bad_update_group_3():
|
||||
# Check remove user in it own group not allowed
|
||||
with pytest.raises(YunohostError):
|
||||
user_group_update("alice", remove_user=["alice"])
|
||||
def test_update_group_all_users_manually(mocker):
|
||||
with raiseYunohostError(mocker, "group_cannot_edit_all_users"):
|
||||
user_group_update("all_users", remove=["alice"])
|
||||
|
||||
def test_bad_update_group_1():
|
||||
# Check add bad user in group
|
||||
with pytest.raises(YunohostError):
|
||||
user_group_update("dev", add_user=["not_exist"])
|
||||
assert "alice" in user_group_list()["groups"]["all_users"]["members"]
|
||||
|
||||
assert "not_exist" not in user_group_list()["groups"]["dev"]
|
||||
|
||||
def test_update_group_primary_manually(mocker):
|
||||
with raiseYunohostError(mocker, "group_cannot_edit_primary_group"):
|
||||
user_group_update("alice", remove=["alice"])
|
||||
|
||||
assert "alice" in user_group_list()["groups"]["alice"]["members"]
|
||||
|
||||
|
||||
def test_update_group_add_user_that_doesnt_exist(mocker):
|
||||
with raiseYunohostError(mocker, "user_unknown"):
|
||||
user_group_update("dev", add=["doesnt_exist"])
|
||||
|
||||
assert "doesnt_exist" not in user_group_list()["groups"]["dev"]["members"]
|
||||
|
|
|
@ -30,29 +30,25 @@ import json
|
|||
import subprocess
|
||||
import pwd
|
||||
import socket
|
||||
from xmlrpclib import Fault
|
||||
from importlib import import_module
|
||||
from collections import OrderedDict
|
||||
|
||||
from moulinette import msignals, m18n
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.process import check_output, call_async_output
|
||||
from moulinette.utils.filesystem import read_json, write_to_json
|
||||
from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron
|
||||
from yunohost.domain import domain_add, domain_list, _get_maindomain, _set_maindomain
|
||||
from moulinette.utils.filesystem import read_json, write_to_json, read_yaml, write_to_yaml
|
||||
from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, _install_appslist_fetch_cron
|
||||
from yunohost.domain import domain_add, domain_list
|
||||
from yunohost.dyndns import _dyndns_available, _dyndns_provides
|
||||
from yunohost.firewall import firewall_upnp
|
||||
from yunohost.service import service_status, service_start, service_enable
|
||||
from yunohost.service import service_start, service_enable
|
||||
from yunohost.regenconf import regen_conf
|
||||
from yunohost.monitor import monitor_disk, monitor_system
|
||||
from yunohost.utils.packages import ynh_packages_version, _dump_sources_list, _list_upgradable_apt_packages
|
||||
from yunohost.utils.network import get_public_ip
|
||||
from yunohost.utils.packages import _dump_sources_list, _list_upgradable_apt_packages
|
||||
from yunohost.utils.error import YunohostError
|
||||
from yunohost.log import is_unit_operation, OperationLogger
|
||||
|
||||
# FIXME this is a duplicate from apps.py
|
||||
APPS_SETTING_PATH = '/etc/yunohost/apps/'
|
||||
MIGRATIONS_STATE_PATH = "/etc/yunohost/migrations_state.json"
|
||||
MIGRATIONS_STATE_PATH = "/etc/yunohost/migrations.yaml"
|
||||
|
||||
logger = getActionLogger('yunohost.tools')
|
||||
|
||||
|
@ -164,60 +160,10 @@ def tools_adminpw(new_password, check_strength=True):
|
|||
logger.success(m18n.n('admin_password_changed'))
|
||||
|
||||
|
||||
@is_unit_operation()
|
||||
def tools_maindomain(operation_logger, new_domain=None):
|
||||
"""
|
||||
Check the current main domain, or change it
|
||||
|
||||
Keyword argument:
|
||||
new_domain -- The new domain to be set as the main domain
|
||||
|
||||
"""
|
||||
|
||||
# If no new domain specified, we return the current main domain
|
||||
if not new_domain:
|
||||
return {'current_main_domain': _get_maindomain()}
|
||||
|
||||
# Check domain exists
|
||||
if new_domain not in domain_list()['domains']:
|
||||
raise YunohostError('domain_unknown')
|
||||
|
||||
operation_logger.related_to.append(('domain', new_domain))
|
||||
operation_logger.start()
|
||||
|
||||
# Apply changes to ssl certs
|
||||
ssl_key = "/etc/ssl/private/yunohost_key.pem"
|
||||
ssl_crt = "/etc/ssl/private/yunohost_crt.pem"
|
||||
new_ssl_key = "/etc/yunohost/certs/%s/key.pem" % new_domain
|
||||
new_ssl_crt = "/etc/yunohost/certs/%s/crt.pem" % new_domain
|
||||
|
||||
try:
|
||||
if os.path.exists(ssl_key) or os.path.lexists(ssl_key):
|
||||
os.remove(ssl_key)
|
||||
if os.path.exists(ssl_crt) or os.path.lexists(ssl_crt):
|
||||
os.remove(ssl_crt)
|
||||
|
||||
os.symlink(new_ssl_key, ssl_key)
|
||||
os.symlink(new_ssl_crt, ssl_crt)
|
||||
|
||||
_set_maindomain(new_domain)
|
||||
except Exception as e:
|
||||
logger.warning("%s" % e, exc_info=1)
|
||||
raise YunohostError('maindomain_change_failed')
|
||||
|
||||
_set_hostname(new_domain)
|
||||
|
||||
# Generate SSOwat configuration file
|
||||
app_ssowatconf()
|
||||
|
||||
# Regen configurations
|
||||
try:
|
||||
with open('/etc/yunohost/installed', 'r'):
|
||||
regen_conf()
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
logger.success(m18n.n('maindomain_changed'))
|
||||
def tools_maindomain(new_main_domain=None):
|
||||
from yunohost.domain import domain_main_domain
|
||||
logger.warning(m18n.g("deprecated_command_alias", prog="yunohost", old="tools maindomain", new="domain main-domain"))
|
||||
return domain_main_domain(new_main_domain=new_main_domain)
|
||||
|
||||
|
||||
def _set_hostname(hostname, pretty_hostname=None):
|
||||
|
@ -281,6 +227,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
|
|||
|
||||
"""
|
||||
from yunohost.utils.password import assert_password_is_strong_enough
|
||||
from yunohost.domain import domain_main_domain
|
||||
|
||||
dyndns_provider = "dyndns.yunohost.org"
|
||||
|
||||
|
@ -350,25 +297,17 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
|
|||
os.system('hostname yunohost.yunohost.org')
|
||||
|
||||
# Add a temporary SSOwat rule to redirect SSO to admin page
|
||||
try:
|
||||
with open('/etc/ssowat/conf.json.persistent') as json_conf:
|
||||
ssowat_conf = json.loads(str(json_conf.read()))
|
||||
except ValueError as e:
|
||||
raise YunohostError('ssowat_persistent_conf_read_error', error=str(e))
|
||||
except IOError:
|
||||
if not os.path.exists('/etc/ssowat/conf.json.persistent'):
|
||||
ssowat_conf = {}
|
||||
else:
|
||||
ssowat_conf = read_json('/etc/ssowat/conf.json.persistent')
|
||||
|
||||
if 'redirected_urls' not in ssowat_conf:
|
||||
ssowat_conf['redirected_urls'] = {}
|
||||
|
||||
ssowat_conf['redirected_urls']['/'] = domain + '/yunohost/admin'
|
||||
|
||||
try:
|
||||
with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
|
||||
json.dump(ssowat_conf, f, sort_keys=True, indent=4)
|
||||
except IOError as e:
|
||||
raise YunohostError('ssowat_persistent_conf_write_error', error=str(e))
|
||||
|
||||
write_to_json('/etc/ssowat/conf.json.persistent', ssowat_conf)
|
||||
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
|
||||
|
||||
# Create SSL CA
|
||||
|
@ -403,7 +342,7 @@ def tools_postinstall(operation_logger, domain, password, ignore_dyndns=False,
|
|||
# New domain config
|
||||
regen_conf(['nsswitch'], force=True)
|
||||
domain_add(domain, dyndns)
|
||||
tools_maindomain(domain)
|
||||
domain_main_domain(domain)
|
||||
|
||||
# Change LDAP admin password
|
||||
tools_adminpw(password, check_strength=not force_password)
|
||||
|
@ -584,10 +523,7 @@ def tools_upgrade(operation_logger, apps=None, system=False):
|
|||
|
||||
upgradable_apps = [app["id"] for app in _list_upgradable_apps()]
|
||||
|
||||
if not upgradable_apps:
|
||||
logger.info(m18n.n("app_no_upgrade"))
|
||||
return
|
||||
elif len(apps) and all(app not in upgradable_apps for app in apps):
|
||||
if not upgradable_apps or (len(apps) and all(app not in upgradable_apps for app in apps)):
|
||||
logger.info(m18n.n("apps_already_up_to_date"))
|
||||
return
|
||||
|
||||
|
@ -619,8 +555,8 @@ def tools_upgrade(operation_logger, apps=None, system=False):
|
|||
# randomly from yunohost itself... upgrading them is likely to
|
||||
critical_packages = ("moulinette", "yunohost", "yunohost-admin", "ssowat", "python")
|
||||
|
||||
critical_packages_upgradable = [p for p in upgradables if p["name"] in critical_packages]
|
||||
noncritical_packages_upgradable = [p for p in upgradables if p["name"] not in critical_packages]
|
||||
critical_packages_upgradable = [p["name"] for p in upgradables if p["name"] in critical_packages]
|
||||
noncritical_packages_upgradable = [p["name"] for p in upgradables if p["name"] not in critical_packages]
|
||||
|
||||
# Prepare dist-upgrade command
|
||||
dist_upgrade = "DEBIAN_FRONTEND=noninteractive"
|
||||
|
@ -651,13 +587,16 @@ def tools_upgrade(operation_logger, apps=None, system=False):
|
|||
|
||||
logger.debug("Running apt command :\n{}".format(dist_upgrade))
|
||||
|
||||
def is_relevant(l):
|
||||
return "Reading database ..." not in l.rstrip()
|
||||
|
||||
callbacks = (
|
||||
lambda l: logger.info("+" + l.rstrip() + "\r"),
|
||||
lambda l: logger.info("+ " + l.rstrip() + "\r") if is_relevant(l) else logger.debug(l.rstrip() + "\r"),
|
||||
lambda l: logger.warning(l.rstrip()),
|
||||
)
|
||||
returncode = call_async_output(dist_upgrade, callbacks, shell=True)
|
||||
if returncode != 0:
|
||||
logger.warning('tools_upgrade_regular_packages_failed',
|
||||
logger.warning(m18n.n('tools_upgrade_regular_packages_failed'),
|
||||
packages_list=', '.join(noncritical_packages_upgradable))
|
||||
operation_logger.error(m18n.n('packages_upgrade_failed'))
|
||||
raise YunohostError(m18n.n('packages_upgrade_failed'))
|
||||
|
@ -729,184 +668,6 @@ def tools_upgrade(operation_logger, apps=None, system=False):
|
|||
operation_logger.success()
|
||||
|
||||
|
||||
def tools_diagnosis(private=False):
|
||||
"""
|
||||
Return global info about current yunohost instance to help debugging
|
||||
|
||||
"""
|
||||
diagnosis = OrderedDict()
|
||||
|
||||
# Debian release
|
||||
try:
|
||||
with open('/etc/debian_version', 'r') as f:
|
||||
debian_version = f.read().rstrip()
|
||||
except IOError as e:
|
||||
logger.warning(m18n.n('diagnosis_debian_version_error', error=format(e)), exc_info=1)
|
||||
else:
|
||||
diagnosis['host'] = "Debian %s" % debian_version
|
||||
|
||||
# Kernel version
|
||||
try:
|
||||
with open('/proc/sys/kernel/osrelease', 'r') as f:
|
||||
kernel_version = f.read().rstrip()
|
||||
except IOError as e:
|
||||
logger.warning(m18n.n('diagnosis_kernel_version_error', error=format(e)), exc_info=1)
|
||||
else:
|
||||
diagnosis['kernel'] = kernel_version
|
||||
|
||||
# Packages version
|
||||
diagnosis['packages'] = ynh_packages_version()
|
||||
|
||||
diagnosis["backports"] = check_output("dpkg -l |awk '/^ii/ && $3 ~ /bpo[6-8]/ {print $2}'").split()
|
||||
|
||||
# Server basic monitoring
|
||||
diagnosis['system'] = OrderedDict()
|
||||
try:
|
||||
disks = monitor_disk(units=['filesystem'], human_readable=True)
|
||||
except (YunohostError, Fault) as e:
|
||||
logger.warning(m18n.n('diagnosis_monitor_disk_error', error=format(e)), exc_info=1)
|
||||
else:
|
||||
diagnosis['system']['disks'] = {}
|
||||
for disk in disks:
|
||||
if isinstance(disks[disk], str):
|
||||
diagnosis['system']['disks'][disk] = disks[disk]
|
||||
else:
|
||||
diagnosis['system']['disks'][disk] = 'Mounted on %s, %s (%s free)' % (
|
||||
disks[disk]['mnt_point'],
|
||||
disks[disk]['size'],
|
||||
disks[disk]['avail']
|
||||
)
|
||||
|
||||
try:
|
||||
system = monitor_system(units=['cpu', 'memory'], human_readable=True)
|
||||
except YunohostError as e:
|
||||
logger.warning(m18n.n('diagnosis_monitor_system_error', error=format(e)), exc_info=1)
|
||||
else:
|
||||
diagnosis['system']['memory'] = {
|
||||
'ram': '%s (%s free)' % (system['memory']['ram']['total'], system['memory']['ram']['free']),
|
||||
'swap': '%s (%s free)' % (system['memory']['swap']['total'], system['memory']['swap']['free']),
|
||||
}
|
||||
|
||||
# nginx -t
|
||||
p = subprocess.Popen("nginx -t".split(),
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT)
|
||||
out, _ = p.communicate()
|
||||
diagnosis["nginx"] = out.strip().split("\n")
|
||||
if p.returncode != 0:
|
||||
logger.error(out)
|
||||
|
||||
# Services status
|
||||
services = service_status()
|
||||
diagnosis['services'] = {}
|
||||
|
||||
for service in services:
|
||||
diagnosis['services'][service] = "%s (%s)" % (services[service]['status'], services[service]['loaded'])
|
||||
|
||||
# YNH Applications
|
||||
try:
|
||||
applications = app_list()['apps']
|
||||
except YunohostError as e:
|
||||
diagnosis['applications'] = m18n.n('diagnosis_no_apps')
|
||||
else:
|
||||
diagnosis['applications'] = {}
|
||||
for application in applications:
|
||||
if application['installed']:
|
||||
diagnosis['applications'][application['id']] = application['label'] if application['label'] else application['name']
|
||||
|
||||
# Private data
|
||||
if private:
|
||||
diagnosis['private'] = OrderedDict()
|
||||
|
||||
# Public IP
|
||||
diagnosis['private']['public_ip'] = {}
|
||||
diagnosis['private']['public_ip']['IPv4'] = get_public_ip(4)
|
||||
diagnosis['private']['public_ip']['IPv6'] = get_public_ip(6)
|
||||
|
||||
# Domains
|
||||
diagnosis['private']['domains'] = domain_list()['domains']
|
||||
|
||||
diagnosis['private']['regen_conf'] = regen_conf(with_diff=True, dry_run=True)
|
||||
|
||||
try:
|
||||
diagnosis['security'] = {
|
||||
"CVE-2017-5754": {
|
||||
"name": "meltdown",
|
||||
"vulnerable": _check_if_vulnerable_to_meltdown(),
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
logger.warning("Unable to check for meltdown vulnerability: %s" % e)
|
||||
|
||||
return diagnosis
|
||||
|
||||
|
||||
def _check_if_vulnerable_to_meltdown():
|
||||
# meltdown CVE: https://security-tracker.debian.org/tracker/CVE-2017-5754
|
||||
|
||||
# We use a cache file to avoid re-running the script so many times,
|
||||
# which can be expensive (up to around 5 seconds on ARM)
|
||||
# and make the admin appear to be slow (c.f. the calls to diagnosis
|
||||
# from the webadmin)
|
||||
#
|
||||
# The cache is in /tmp and shall disappear upon reboot
|
||||
# *or* we compare it to dpkg.log modification time
|
||||
# such that it's re-ran if there was package upgrades
|
||||
# (e.g. from yunohost)
|
||||
cache_file = "/tmp/yunohost-meltdown-diagnosis"
|
||||
dpkg_log = "/var/log/dpkg.log"
|
||||
if os.path.exists(cache_file):
|
||||
if not os.path.exists(dpkg_log) or os.path.getmtime(cache_file) > os.path.getmtime(dpkg_log):
|
||||
logger.debug("Using cached results for meltdown checker, from %s" % cache_file)
|
||||
return read_json(cache_file)[0]["VULNERABLE"]
|
||||
|
||||
# script taken from https://github.com/speed47/spectre-meltdown-checker
|
||||
# script commit id is store directly in the script
|
||||
file_dir = os.path.split(__file__)[0]
|
||||
SCRIPT_PATH = os.path.join(file_dir, "./vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh")
|
||||
|
||||
# '--variant 3' corresponds to Meltdown
|
||||
# example output from the script:
|
||||
# [{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}]
|
||||
try:
|
||||
logger.debug("Running meltdown vulnerability checker")
|
||||
call = subprocess.Popen("bash %s --batch json --variant 3" %
|
||||
SCRIPT_PATH, shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
|
||||
# TODO / FIXME : here we are ignoring error messages ...
|
||||
# in particular on RPi2 and other hardware, the script complains about
|
||||
# "missing some kernel info (see -v), accuracy might be reduced"
|
||||
# Dunno what to do about that but we probably don't want to harass
|
||||
# users with this warning ...
|
||||
output, err = call.communicate()
|
||||
assert call.returncode in (0, 2, 3), "Return code: %s" % call.returncode
|
||||
|
||||
# If there are multiple lines, sounds like there was some messages
|
||||
# in stdout that are not json >.> ... Try to get the actual json
|
||||
# stuff which should be the last line
|
||||
output = output.strip()
|
||||
if "\n" in output:
|
||||
logger.debug("Original meltdown checker output : %s" % output)
|
||||
output = output.split("\n")[-1]
|
||||
|
||||
CVEs = json.loads(output)
|
||||
assert len(CVEs) == 1
|
||||
assert CVEs[0]["NAME"] == "MELTDOWN"
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
logger.warning("Something wrong happened when trying to diagnose Meltdown vunerability, exception: %s" % e)
|
||||
raise Exception("Command output for failed meltdown check: '%s'" % output)
|
||||
|
||||
logger.debug("Writing results from meltdown checker to cache file, %s" % cache_file)
|
||||
write_to_json(cache_file, CVEs)
|
||||
return CVEs[0]["VULNERABLE"]
|
||||
|
||||
|
||||
@is_unit_operation()
|
||||
def tools_shutdown(operation_logger, force=False):
|
||||
shutdown = force
|
||||
|
@ -944,191 +705,6 @@ def tools_reboot(operation_logger, force=False):
|
|||
subprocess.check_call(['systemctl', 'reboot'])
|
||||
|
||||
|
||||
def tools_migrations_list(pending=False, done=False):
|
||||
"""
|
||||
List existing migrations
|
||||
"""
|
||||
|
||||
# Check for option conflict
|
||||
if pending and done:
|
||||
raise YunohostError("migrations_list_conflict_pending_done")
|
||||
|
||||
# Get all migrations
|
||||
migrations = _get_migrations_list()
|
||||
|
||||
# If asked, filter pending or done migrations
|
||||
if pending or done:
|
||||
last_migration = tools_migrations_state()["last_run_migration"]
|
||||
last_migration = last_migration["number"] if last_migration else -1
|
||||
if done:
|
||||
migrations = [m for m in migrations if m.number <= last_migration]
|
||||
if pending:
|
||||
migrations = [m for m in migrations if m.number > last_migration]
|
||||
|
||||
# Reduce to dictionnaries
|
||||
migrations = [{"id": migration.id,
|
||||
"number": migration.number,
|
||||
"name": migration.name,
|
||||
"mode": migration.mode,
|
||||
"description": migration.description,
|
||||
"disclaimer": migration.disclaimer} for migration in migrations]
|
||||
|
||||
return {"migrations": migrations}
|
||||
|
||||
|
||||
def tools_migrations_migrate(target=None, skip=False, auto=False, accept_disclaimer=False):
|
||||
"""
|
||||
Perform migrations
|
||||
"""
|
||||
|
||||
# state is a datastructure that represents the last run migration
|
||||
# it has this form:
|
||||
# {
|
||||
# "last_run_migration": {
|
||||
# "number": "00xx",
|
||||
# "name": "some name",
|
||||
# }
|
||||
# }
|
||||
state = tools_migrations_state()
|
||||
|
||||
last_run_migration_number = state["last_run_migration"]["number"] if state["last_run_migration"] else 0
|
||||
|
||||
# load all migrations
|
||||
migrations = _get_migrations_list()
|
||||
migrations = sorted(migrations, key=lambda x: x.number)
|
||||
|
||||
if not migrations:
|
||||
logger.info(m18n.n('migrations_no_migrations_to_run'))
|
||||
return
|
||||
|
||||
all_migration_numbers = [x.number for x in migrations]
|
||||
|
||||
if target is None:
|
||||
target = migrations[-1].number
|
||||
|
||||
# validate input, target must be "0" or a valid number
|
||||
elif target != 0 and target not in all_migration_numbers:
|
||||
raise YunohostError('migrations_bad_value_for_target', ", ".join(map(str, all_migration_numbers)))
|
||||
|
||||
logger.debug(m18n.n('migrations_current_target', target))
|
||||
|
||||
# no new migrations to run
|
||||
if target == last_run_migration_number:
|
||||
logger.info(m18n.n('migrations_no_migrations_to_run'))
|
||||
return
|
||||
|
||||
logger.debug(m18n.n('migrations_show_last_migration', last_run_migration_number))
|
||||
|
||||
# we need to run missing migrations
|
||||
if last_run_migration_number < target:
|
||||
logger.debug(m18n.n('migrations_forward'))
|
||||
# drop all already run migrations
|
||||
migrations = filter(lambda x: target >= x.number > last_run_migration_number, migrations)
|
||||
mode = "forward"
|
||||
|
||||
# we need to go backward on already run migrations
|
||||
elif last_run_migration_number > target:
|
||||
logger.debug(m18n.n('migrations_backward'))
|
||||
# drop all not already run migrations
|
||||
migrations = filter(lambda x: target < x.number <= last_run_migration_number, migrations)
|
||||
mode = "backward"
|
||||
|
||||
else: # can't happen, this case is handle before
|
||||
raise Exception()
|
||||
|
||||
# effectively run selected migrations
|
||||
for migration in migrations:
|
||||
|
||||
if not skip:
|
||||
# If we are migrating in "automatic mode" (i.e. from debian configure
|
||||
# during an upgrade of the package) but we are asked to run migrations
|
||||
# to be ran manually by the user, stop there and ask the user to
|
||||
# run the migration manually.
|
||||
if auto and migration.mode == "manual":
|
||||
logger.warn(m18n.n('migrations_to_be_ran_manually',
|
||||
number=migration.number,
|
||||
name=migration.name))
|
||||
break
|
||||
|
||||
# If some migrations have disclaimers,
|
||||
if migration.disclaimer:
|
||||
# require the --accept-disclaimer option. Otherwise, stop everything
|
||||
# here and display the disclaimer
|
||||
if not accept_disclaimer:
|
||||
logger.warn(m18n.n('migrations_need_to_accept_disclaimer',
|
||||
number=migration.number,
|
||||
name=migration.name,
|
||||
disclaimer=migration.disclaimer))
|
||||
break
|
||||
# --accept-disclaimer will only work for the first migration
|
||||
else:
|
||||
accept_disclaimer = False
|
||||
|
||||
# Start register change on system
|
||||
operation_logger = OperationLogger('tools_migrations_migrate_' + mode)
|
||||
operation_logger.start()
|
||||
|
||||
if not skip:
|
||||
|
||||
logger.info(m18n.n('migrations_show_currently_running_migration',
|
||||
number=migration.number, name=migration.name))
|
||||
|
||||
try:
|
||||
migration.operation_logger = operation_logger
|
||||
if mode == "forward":
|
||||
migration.migrate()
|
||||
elif mode == "backward":
|
||||
migration.backward()
|
||||
else: # can't happen
|
||||
raise Exception("Illegal state for migration: '%s', should be either 'forward' or 'backward'" % mode)
|
||||
except Exception as e:
|
||||
# migration failed, let's stop here but still update state because
|
||||
# we managed to run the previous ones
|
||||
msg = m18n.n('migrations_migration_has_failed',
|
||||
exception=e,
|
||||
number=migration.number,
|
||||
name=migration.name)
|
||||
logger.error(msg, exc_info=1)
|
||||
operation_logger.error(msg)
|
||||
break
|
||||
else:
|
||||
logger.success(m18n.n('migrations_success',
|
||||
number=migration.number, name=migration.name))
|
||||
|
||||
else: # if skip
|
||||
logger.warn(m18n.n('migrations_skip_migration',
|
||||
number=migration.number,
|
||||
name=migration.name))
|
||||
|
||||
# update the state to include the latest run migration
|
||||
state["last_run_migration"] = {
|
||||
"number": migration.number,
|
||||
"name": migration.name
|
||||
}
|
||||
|
||||
operation_logger.success()
|
||||
|
||||
# Skip migrations one at a time
|
||||
if skip:
|
||||
break
|
||||
|
||||
# special case where we want to go back from the start
|
||||
if target == 0:
|
||||
state["last_run_migration"] = None
|
||||
|
||||
write_to_json(MIGRATIONS_STATE_PATH, state)
|
||||
|
||||
|
||||
def tools_migrations_state():
|
||||
"""
|
||||
Show current migration state
|
||||
"""
|
||||
if not os.path.exists(MIGRATIONS_STATE_PATH):
|
||||
return {"last_run_migration": None}
|
||||
|
||||
return read_json(MIGRATIONS_STATE_PATH)
|
||||
|
||||
|
||||
def tools_shell(command=None):
|
||||
"""
|
||||
Launch an (i)python shell in the YunoHost context.
|
||||
|
@ -1160,6 +736,210 @@ def tools_shell(command=None):
|
|||
shell.interact()
|
||||
|
||||
|
||||
# ############################################ #
|
||||
# #
|
||||
# Migrations management #
|
||||
# #
|
||||
# ############################################ #
|
||||
|
||||
def tools_migrations_list(pending=False, done=False):
|
||||
"""
|
||||
List existing migrations
|
||||
"""
|
||||
|
||||
# Check for option conflict
|
||||
if pending and done:
|
||||
raise YunohostError("migrations_list_conflict_pending_done")
|
||||
|
||||
# Get all migrations
|
||||
migrations = _get_migrations_list()
|
||||
|
||||
# Reduce to dictionnaries
|
||||
migrations = [{"id": migration.id,
|
||||
"number": migration.number,
|
||||
"name": migration.name,
|
||||
"mode": migration.mode,
|
||||
"state": migration.state,
|
||||
"description": migration.description,
|
||||
"disclaimer": migration.disclaimer} for migration in migrations]
|
||||
|
||||
# If asked, filter pending or done migrations
|
||||
if pending or done:
|
||||
if done:
|
||||
migrations = [m for m in migrations if m["state"] != "pending"]
|
||||
if pending:
|
||||
migrations = [m for m in migrations if m["state"] == "pending"]
|
||||
|
||||
return {"migrations": migrations}
|
||||
|
||||
|
||||
def tools_migrations_migrate(targets=[], skip=False, auto=False, force_rerun=False, accept_disclaimer=False):
|
||||
"""
|
||||
Perform migrations
|
||||
|
||||
targets A list migrations to run (all pendings by default)
|
||||
--skip Skip specified migrations (to be used only if you know what you are doing) (must explicit which migrations)
|
||||
--auto Automatic mode, won't run manual migrations (to be used only if you know what you are doing)
|
||||
--force-rerun Re-run already-ran migrations (to be used only if you know what you are doing)(must explicit which migrations)
|
||||
--accept-disclaimer Accept disclaimers of migrations (please read them before using this option) (only valid for one migration)
|
||||
"""
|
||||
|
||||
all_migrations = _get_migrations_list()
|
||||
|
||||
# Small utility that allows up to get a migration given a name, id or number later
|
||||
def get_matching_migration(target):
|
||||
for m in all_migrations:
|
||||
if m.id == target or m.name == target or m.id.split("_")[0] == target:
|
||||
return m
|
||||
|
||||
raise YunohostError("migrations_no_such_migration", id=target)
|
||||
|
||||
# auto, skip and force are exclusive options
|
||||
if auto + skip + force_rerun > 1:
|
||||
raise YunohostError("migrations_exclusive_options")
|
||||
|
||||
# If no target specified
|
||||
if not targets:
|
||||
# skip, revert or force require explicit targets
|
||||
if (skip or force_rerun):
|
||||
raise YunohostError("migrations_must_provide_explicit_targets")
|
||||
|
||||
# Otherwise, targets are all pending migrations
|
||||
targets = [m for m in all_migrations if m.state == "pending"]
|
||||
|
||||
# If explicit targets are provided, we shall validate them
|
||||
else:
|
||||
targets = [get_matching_migration(t) for t in targets]
|
||||
done = [t.id for t in targets if t.state != "pending"]
|
||||
pending = [t.id for t in targets if t.state == "pending"]
|
||||
|
||||
if skip and done:
|
||||
raise YunohostError("migrations_not_pending_cant_skip", ids=', '.join(done))
|
||||
if force_rerun and pending:
|
||||
raise YunohostError("migrations_pending_cant_rerun", ids=', '.join(pending))
|
||||
if not (skip or force_rerun) and done:
|
||||
raise YunohostError("migrations_already_ran", ids=', '.join(done))
|
||||
|
||||
# So, is there actually something to do ?
|
||||
if not targets:
|
||||
logger.info(m18n.n('migrations_no_migrations_to_run'))
|
||||
return
|
||||
|
||||
# Actually run selected migrations
|
||||
for migration in targets:
|
||||
|
||||
# If we are migrating in "automatic mode" (i.e. from debian configure
|
||||
# during an upgrade of the package) but we are asked for running
|
||||
# migrations to be ran manually by the user, stop there and ask the
|
||||
# user to run the migration manually.
|
||||
if auto and migration.mode == "manual":
|
||||
logger.warn(m18n.n('migrations_to_be_ran_manually', id=migration.id))
|
||||
|
||||
# We go to the next migration
|
||||
continue
|
||||
|
||||
# Check for migration dependencies
|
||||
if not skip:
|
||||
dependencies = [get_matching_migration(dep) for dep in migration.dependencies]
|
||||
pending_dependencies = [dep.id for dep in dependencies if dep.state == "pending"]
|
||||
if pending_dependencies:
|
||||
logger.error(m18n.n('migrations_dependencies_not_satisfied',
|
||||
id=migration.id,
|
||||
dependencies_id=', '.join(pending_dependencies)))
|
||||
continue
|
||||
|
||||
# If some migrations have disclaimers (and we're not trying to skip them)
|
||||
if migration.disclaimer and not skip:
|
||||
# require the --accept-disclaimer option.
|
||||
# Otherwise, go to the next migration
|
||||
if not accept_disclaimer:
|
||||
logger.warn(m18n.n('migrations_need_to_accept_disclaimer',
|
||||
id=migration.id,
|
||||
disclaimer=migration.disclaimer))
|
||||
continue
|
||||
# --accept-disclaimer will only work for the first migration
|
||||
else:
|
||||
accept_disclaimer = False
|
||||
|
||||
# Start register change on system
|
||||
operation_logger = OperationLogger('tools_migrations_migrate_forward')
|
||||
operation_logger.start()
|
||||
|
||||
if skip:
|
||||
logger.warn(m18n.n('migrations_skip_migration', id=migration.id))
|
||||
migration.state = "skipped"
|
||||
_write_migration_state(migration.id, "skipped")
|
||||
operation_logger.success()
|
||||
else:
|
||||
|
||||
try:
|
||||
migration.operation_logger = operation_logger
|
||||
logger.info(m18n.n('migrations_running_forward', id=migration.id))
|
||||
migration.run()
|
||||
except Exception as e:
|
||||
# migration failed, let's stop here but still update state because
|
||||
# we managed to run the previous ones
|
||||
msg = m18n.n('migrations_migration_has_failed',
|
||||
exception=e, id=migration.id)
|
||||
logger.error(msg, exc_info=1)
|
||||
operation_logger.error(msg)
|
||||
else:
|
||||
logger.success(m18n.n('migrations_success_forward', id=migration.id))
|
||||
migration.state = "done"
|
||||
_write_migration_state(migration.id, "done")
|
||||
|
||||
operation_logger.success()
|
||||
|
||||
|
||||
def tools_migrations_state():
|
||||
"""
|
||||
Show current migration state
|
||||
"""
|
||||
if os.path.exists("/etc/yunohost/migrations_state.json"):
|
||||
_migrate_legacy_migration_json()
|
||||
|
||||
if not os.path.exists(MIGRATIONS_STATE_PATH):
|
||||
return {"migrations": {}}
|
||||
|
||||
return read_yaml(MIGRATIONS_STATE_PATH)
|
||||
|
||||
|
||||
def _migrate_legacy_migration_json():
|
||||
|
||||
from moulinette.utils.filesystem import read_json
|
||||
|
||||
logger.debug("Migrating legacy migration state json to yaml...")
|
||||
|
||||
# We fetch the old state containing the last run migration
|
||||
old_state = read_json("/etc/yunohost/migrations_state.json")["last_run_migration"]
|
||||
last_run_migration_id = str(old_state["number"]) + "_" + old_state["name"]
|
||||
|
||||
# Extract the list of migration ids
|
||||
from . import data_migrations
|
||||
migrations_path = data_migrations.__path__[0]
|
||||
migration_files = filter(lambda x: re.match("^\d+_[a-zA-Z0-9_]+\.py$", x), os.listdir(migrations_path))
|
||||
# (here we remove the .py extension and make sure the ids are sorted)
|
||||
migration_ids = sorted([f.rsplit(".", 1)[0] for f in migration_files])
|
||||
|
||||
# So now build the new dict for every id up to the last run migration
|
||||
migrations = {}
|
||||
for migration_id in migration_ids:
|
||||
migrations[migration_id] = "done"
|
||||
if last_run_migration_id in migration_id:
|
||||
break
|
||||
|
||||
# Write the new file and rename the old one
|
||||
write_to_yaml(MIGRATIONS_STATE_PATH, {"migrations": migrations})
|
||||
os.rename("/etc/yunohost/migrations_state.json", "/etc/yunohost/migrations_state.json.old")
|
||||
|
||||
|
||||
def _write_migration_state(migration_id, state):
|
||||
|
||||
current_states = tools_migrations_state()
|
||||
current_states["migrations"][migration_id] = state
|
||||
write_to_yaml(MIGRATIONS_STATE_PATH, current_states)
|
||||
|
||||
|
||||
def _get_migrations_list():
|
||||
migrations = []
|
||||
|
||||
|
@ -1175,8 +955,21 @@ def _get_migrations_list():
|
|||
logger.warn(m18n.n('migrations_cant_reach_migration_file', migrations_path))
|
||||
return migrations
|
||||
|
||||
# states is a datastructure that represents the last run migration
|
||||
# it has this form:
|
||||
# {
|
||||
# "0001_foo": "skipped",
|
||||
# "0004_baz": "done",
|
||||
# "0002_bar": "skipped",
|
||||
# "0005_zblerg": "done",
|
||||
# }
|
||||
# (in particular, pending migrations / not already ran are not listed
|
||||
states = tools_migrations_state()["migrations"]
|
||||
|
||||
for migration_file in filter(lambda x: re.match("^\d+_[a-zA-Z0-9_]+\.py$", x), os.listdir(migrations_path)):
|
||||
migrations.append(_load_migration(migration_file))
|
||||
m = _load_migration(migration_file)
|
||||
m.state = states.get(m.id, "pending")
|
||||
migrations.append(m)
|
||||
|
||||
return sorted(migrations, key=lambda m: m.id)
|
||||
|
||||
|
@ -1203,10 +996,7 @@ def _load_migration(migration_file):
|
|||
|
||||
migration_id = migration_file[:-len(".py")]
|
||||
|
||||
number, name = migration_id.split("_", 1)
|
||||
|
||||
logger.debug(m18n.n('migrations_loading_migration',
|
||||
number=number, name=name))
|
||||
logger.debug(m18n.n('migrations_loading_migration', id=migration_id))
|
||||
|
||||
try:
|
||||
# this is python builtin method to import a module using a name, we
|
||||
|
@ -1214,12 +1004,11 @@ def _load_migration(migration_file):
|
|||
# able to run it in the next loop
|
||||
module = import_module("yunohost.data_migrations.{}".format(migration_id))
|
||||
return module.MyMigration(migration_id)
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
raise YunohostError('migrations_error_failed_to_load_migration',
|
||||
number=number, name=name)
|
||||
raise YunohostError('migrations_failed_to_load_migration', id=migration_id, error=e)
|
||||
|
||||
|
||||
def _skip_all_migrations():
|
||||
|
@ -1228,18 +1017,11 @@ def _skip_all_migrations():
|
|||
This is meant to be used during postinstall to
|
||||
initialize the migration system.
|
||||
"""
|
||||
state = tools_migrations_state()
|
||||
|
||||
# load all migrations
|
||||
migrations = _get_migrations_list()
|
||||
migrations = sorted(migrations, key=lambda x: x.number)
|
||||
last_migration = migrations[-1]
|
||||
|
||||
state["last_run_migration"] = {
|
||||
"number": last_migration.number,
|
||||
"name": last_migration.name
|
||||
}
|
||||
write_to_json(MIGRATIONS_STATE_PATH, state)
|
||||
all_migrations = _get_migrations_list()
|
||||
new_states = {"migrations": {}}
|
||||
for migration in all_migrations:
|
||||
new_states["migrations"][migration.id] = "skipped"
|
||||
write_to_yaml(MIGRATIONS_STATE_PATH, new_states)
|
||||
|
||||
|
||||
class Migration(object):
|
||||
|
@ -1247,21 +1029,16 @@ class Migration(object):
|
|||
# Those are to be implemented by daughter classes
|
||||
|
||||
mode = "auto"
|
||||
|
||||
def forward(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def backward(self):
|
||||
pass
|
||||
dependencies = [] # List of migration ids required before running this migration
|
||||
|
||||
@property
|
||||
def disclaimer(self):
|
||||
return None
|
||||
|
||||
# The followings shouldn't be overriden
|
||||
def run(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def migrate(self):
|
||||
self.forward()
|
||||
# The followings shouldn't be overriden
|
||||
|
||||
def __init__(self, id_):
|
||||
self.id = id_
|
||||
|
|
|
@ -32,10 +32,13 @@ import crypt
|
|||
import random
|
||||
import string
|
||||
import subprocess
|
||||
import copy
|
||||
|
||||
from moulinette import m18n
|
||||
from yunohost.utils.error import YunohostError
|
||||
from moulinette.utils.log import getActionLogger
|
||||
from moulinette.utils.filesystem import read_json, write_to_json, read_yaml, write_to_yaml
|
||||
|
||||
from yunohost.utils.error import YunohostError
|
||||
from yunohost.service import service_status
|
||||
from yunohost.log import is_unit_operation
|
||||
|
||||
|
@ -126,12 +129,18 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
|
|||
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
if username in user_list()["users"]:
|
||||
raise YunohostError("user_already_exists", user=username)
|
||||
|
||||
# Validate uniqueness of username and mail in LDAP
|
||||
ldap.validate_uniqueness({
|
||||
'uid': username,
|
||||
'mail': mail,
|
||||
'cn': username
|
||||
})
|
||||
try:
|
||||
ldap.validate_uniqueness({
|
||||
'uid': username,
|
||||
'mail': mail,
|
||||
'cn': username
|
||||
})
|
||||
except Exception as e:
|
||||
raise YunohostError('user_creation_failed', user=username, error=e)
|
||||
|
||||
# Validate uniqueness of username in system users
|
||||
all_existing_usernames = {x.pw_name for x in pwd.getpwall()}
|
||||
|
@ -188,49 +197,45 @@ def user_create(operation_logger, username, firstname, lastname, mail, password,
|
|||
attr_dict['mail'] = [attr_dict['mail']] + aliases
|
||||
|
||||
# If exists, remove the redirection from the SSO
|
||||
try:
|
||||
with open('/etc/ssowat/conf.json.persistent') as json_conf:
|
||||
ssowat_conf = json.loads(str(json_conf.read()))
|
||||
except ValueError as e:
|
||||
raise YunohostError('ssowat_persistent_conf_read_error', error=e.strerror)
|
||||
except IOError:
|
||||
if not os.path.exists('/etc/ssowat/conf.json.persistent'):
|
||||
ssowat_conf = {}
|
||||
else:
|
||||
ssowat_conf = read_json('/etc/ssowat/conf.json.persistent')
|
||||
|
||||
if 'redirected_urls' in ssowat_conf and '/' in ssowat_conf['redirected_urls']:
|
||||
del ssowat_conf['redirected_urls']['/']
|
||||
try:
|
||||
with open('/etc/ssowat/conf.json.persistent', 'w+') as f:
|
||||
json.dump(ssowat_conf, f, sort_keys=True, indent=4)
|
||||
except IOError as e:
|
||||
raise YunohostError('ssowat_persistent_conf_write_error', error=e.strerror)
|
||||
|
||||
if ldap.add('uid=%s,ou=users' % username, attr_dict):
|
||||
# Invalidate passwd to take user creation into account
|
||||
subprocess.call(['nscd', '-i', 'passwd'])
|
||||
write_to_json('/etc/ssowat/conf.json.persistent', ssowat_conf)
|
||||
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
|
||||
|
||||
try:
|
||||
# Attempt to create user home folder
|
||||
subprocess.check_call(
|
||||
['su', '-', username, '-c', "''"])
|
||||
except subprocess.CalledProcessError:
|
||||
if not os.path.isdir('/home/{0}'.format(username)):
|
||||
logger.warning(m18n.n('user_home_creation_failed'),
|
||||
exc_info=1)
|
||||
try:
|
||||
ldap.add('uid=%s,ou=users' % username, attr_dict)
|
||||
except Exception as e:
|
||||
raise YunohostError('user_creation_failed', user=username, error=e)
|
||||
|
||||
# Create group for user and add to group 'all_users'
|
||||
user_group_add(groupname=username, gid=uid, sync_perm=False)
|
||||
user_group_update(groupname=username, add_user=username, force=True, sync_perm=False)
|
||||
user_group_update(groupname='all_users', add_user=username, force=True, sync_perm=True)
|
||||
# Invalidate passwd to take user creation into account
|
||||
subprocess.call(['nscd', '-i', 'passwd'])
|
||||
|
||||
# TODO: Send a welcome mail to user
|
||||
logger.success(m18n.n('user_created'))
|
||||
try:
|
||||
# Attempt to create user home folder
|
||||
subprocess.check_call(
|
||||
['su', '-', username, '-c', "''"])
|
||||
except subprocess.CalledProcessError:
|
||||
if not os.path.isdir('/home/{0}'.format(username)):
|
||||
logger.warning(m18n.n('user_home_creation_failed'),
|
||||
exc_info=1)
|
||||
|
||||
hook_callback('post_user_create',
|
||||
args=[username, mail, password, firstname, lastname])
|
||||
# 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_update(groupname='all_users', add=username, force=True, sync_perm=True)
|
||||
|
||||
return {'fullname': fullname, 'username': username, 'mail': mail}
|
||||
# TODO: Send a welcome mail to user
|
||||
logger.success(m18n.n('user_created'))
|
||||
|
||||
raise YunohostError('user_creation_failed')
|
||||
hook_callback('post_user_create',
|
||||
args=[username, mail, password, firstname, lastname])
|
||||
|
||||
return {'fullname': fullname, 'username': username, 'mail': mail}
|
||||
|
||||
|
||||
@is_unit_operation([('username', 'user')])
|
||||
|
@ -245,32 +250,40 @@ def user_delete(operation_logger, username, purge=False):
|
|||
"""
|
||||
from yunohost.hook import hook_callback
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
from yunohost.permission import permission_sync_to_user
|
||||
|
||||
if username not in user_list()["users"]:
|
||||
raise YunohostError('user_unknown', user=username)
|
||||
|
||||
operation_logger.start()
|
||||
|
||||
user_group_update("all_users", remove=username, force=True, sync_perm=False)
|
||||
for group, infos in user_group_list()["groups"].items():
|
||||
if group == "all_users":
|
||||
continue
|
||||
# If the user is in this group (and it's not the primary group),
|
||||
# remove the member from the group
|
||||
if username != group and username in infos["members"]:
|
||||
user_group_update(group, remove=username, sync_perm=False)
|
||||
|
||||
# Delete primary group if it exists (why wouldnt it exists ? because some
|
||||
# epic bug happened somewhere else and only a partial removal was
|
||||
# performed...)
|
||||
if username in user_group_list()['groups'].keys():
|
||||
user_group_delete(username, force=True, sync_perm=True)
|
||||
|
||||
ldap = _get_ldap_interface()
|
||||
if ldap.remove('uid=%s,ou=users' % username):
|
||||
# Invalidate passwd to take user deletion into account
|
||||
subprocess.call(['nscd', '-i', 'passwd'])
|
||||
try:
|
||||
ldap.remove('uid=%s,ou=users' % username)
|
||||
except Exception as e:
|
||||
raise YunohostError('user_deletion_failed', user=username, error=e)
|
||||
|
||||
if purge:
|
||||
subprocess.call(['rm', '-rf', '/home/{0}'.format(username)])
|
||||
subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)])
|
||||
else:
|
||||
raise YunohostError('user_deletion_failed')
|
||||
# Invalidate passwd to take user deletion into account
|
||||
subprocess.call(['nscd', '-i', 'passwd'])
|
||||
|
||||
user_group_delete(username, force=True, sync_perm=True)
|
||||
|
||||
group_list = ldap.search('ou=groups,dc=yunohost,dc=org',
|
||||
'(&(objectclass=groupOfNamesYnh)(memberUid=%s))'
|
||||
% username, ['cn'])
|
||||
for group in group_list:
|
||||
user_list = ldap.search('ou=groups,dc=yunohost,dc=org',
|
||||
'cn=' + group['cn'][0],
|
||||
['memberUid'])[0]
|
||||
user_list['memberUid'].remove(username)
|
||||
if not ldap.update('cn=%s,ou=groups' % group['cn'][0], user_list):
|
||||
raise YunohostError('group_update_failed')
|
||||
if purge:
|
||||
subprocess.call(['rm', '-rf', '/home/{0}'.format(username)])
|
||||
subprocess.call(['rm', '-rf', '/var/mail/{0}'.format(username)])
|
||||
|
||||
hook_callback('post_user_delete', args=[username, purge])
|
||||
|
||||
|
@ -338,7 +351,10 @@ def user_update(operation_logger, username, firstname=None, lastname=None, mail=
|
|||
'webmaster@' + main_domain,
|
||||
'postmaster@' + main_domain,
|
||||
]
|
||||
ldap.validate_uniqueness({'mail': mail})
|
||||
try:
|
||||
ldap.validate_uniqueness({'mail': mail})
|
||||
except Exception as e:
|
||||
raise YunohostError('user_update_failed', user=username, error=e)
|
||||
if mail[mail.find('@') + 1:] not in domains:
|
||||
raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:])
|
||||
if mail in aliases:
|
||||
|
@ -351,7 +367,10 @@ def user_update(operation_logger, username, firstname=None, lastname=None, mail=
|
|||
if not isinstance(add_mailalias, list):
|
||||
add_mailalias = [add_mailalias]
|
||||
for mail in add_mailalias:
|
||||
ldap.validate_uniqueness({'mail': mail})
|
||||
try:
|
||||
ldap.validate_uniqueness({'mail': mail})
|
||||
except Exception as e:
|
||||
raise YunohostError('user_update_failed', user=username, error=e)
|
||||
if mail[mail.find('@') + 1:] not in domains:
|
||||
raise YunohostError('mail_domain_unknown', domain=mail[mail.find('@') + 1:])
|
||||
user['mail'].append(mail)
|
||||
|
@ -391,12 +410,14 @@ def user_update(operation_logger, username, firstname=None, lastname=None, mail=
|
|||
|
||||
operation_logger.start()
|
||||
|
||||
if ldap.update('uid=%s,ou=users' % username, new_attr_dict):
|
||||
logger.success(m18n.n('user_updated'))
|
||||
app_ssowatconf()
|
||||
return user_info(username)
|
||||
else:
|
||||
raise YunohostError('user_update_failed')
|
||||
try:
|
||||
ldap.update('uid=%s,ou=users' % username, new_attr_dict)
|
||||
except Exception as e:
|
||||
raise YunohostError('user_update_failed', user=username, error=e)
|
||||
|
||||
logger.success(m18n.n('user_updated'))
|
||||
app_ssowatconf()
|
||||
return user_info(username)
|
||||
|
||||
|
||||
def user_info(username):
|
||||
|
@ -453,7 +474,7 @@ def user_info(username):
|
|||
|
||||
if service_status("dovecot")["status"] != "running":
|
||||
logger.warning(m18n.n('mailbox_used_space_dovecot_down'))
|
||||
elif not user_permission_list(app="mail", permission="main", username=username)['permissions']:
|
||||
elif username not in user_permission_list(full=True)["permissions"]["mail.main"]["corresponding_users"]:
|
||||
logger.warning(m18n.n('mailbox_disabled', user=username))
|
||||
else:
|
||||
cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0]
|
||||
|
@ -480,81 +501,59 @@ def user_info(username):
|
|||
'use': storage_use
|
||||
}
|
||||
|
||||
if result:
|
||||
return result_dict
|
||||
else:
|
||||
raise YunohostError('user_info_failed')
|
||||
return result_dict
|
||||
|
||||
|
||||
#
|
||||
# Group subcategory
|
||||
#
|
||||
def user_group_list(fields=None):
|
||||
def user_group_list(short=False, full=False, include_primary_groups=True):
|
||||
"""
|
||||
List users
|
||||
|
||||
Keyword argument:
|
||||
filter -- LDAP filter used to search
|
||||
offset -- Starting number for user fetching
|
||||
limit -- Maximum number of user fetched
|
||||
fields -- fields to fetch
|
||||
|
||||
short -- Only list the name of the groups without any additional info
|
||||
full -- List all the info available for each groups
|
||||
include_primary_groups -- Include groups corresponding to users (which should always only contains this user)
|
||||
This option is set to false by default in the action map because we don't want to have
|
||||
these displayed when the user runs `yunohost user group list`, but internally we do want
|
||||
to list them when called from other functions
|
||||
"""
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
|
||||
# Fetch relevant informations
|
||||
|
||||
from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract
|
||||
ldap = _get_ldap_interface()
|
||||
group_attr = {
|
||||
'cn': 'groupname',
|
||||
'member': 'members',
|
||||
'permission': 'permission'
|
||||
}
|
||||
attrs = ['cn']
|
||||
groups_infos = ldap.search('ou=groups,dc=yunohost,dc=org',
|
||||
'(objectclass=groupOfNamesYnh)',
|
||||
["cn", "member", "permission"])
|
||||
|
||||
# Parse / organize information to be outputed
|
||||
|
||||
users = user_list()["users"]
|
||||
groups = {}
|
||||
for infos in groups_infos:
|
||||
|
||||
if fields:
|
||||
keys = group_attr.keys()
|
||||
for attr in fields:
|
||||
if attr in keys:
|
||||
attrs.append(attr)
|
||||
else:
|
||||
raise YunohostError('field_invalid', attr)
|
||||
else:
|
||||
attrs = ['cn', 'member']
|
||||
name = infos["cn"][0]
|
||||
|
||||
result = ldap.search('ou=groups,dc=yunohost,dc=org',
|
||||
'(objectclass=groupOfNamesYnh)',
|
||||
attrs)
|
||||
|
||||
for group in result:
|
||||
# The group "admins" should be hidden for the user
|
||||
if group_attr['cn'] == "admins":
|
||||
if not include_primary_groups and name in users:
|
||||
continue
|
||||
entry = {}
|
||||
for attr, values in group.items():
|
||||
if values:
|
||||
if attr == "member":
|
||||
entry[group_attr[attr]] = []
|
||||
for v in values:
|
||||
entry[group_attr[attr]].append(v.split("=")[1].split(",")[0])
|
||||
elif attr == "permission":
|
||||
entry[group_attr[attr]] = {}
|
||||
for v in values:
|
||||
permission = v.split("=")[1].split(",")[0].split(".")[1]
|
||||
pType = v.split("=")[1].split(",")[0].split(".")[0]
|
||||
if permission in entry[group_attr[attr]]:
|
||||
entry[group_attr[attr]][permission].append(pType)
|
||||
else:
|
||||
entry[group_attr[attr]][permission] = [pType]
|
||||
else:
|
||||
entry[group_attr[attr]] = values[0]
|
||||
|
||||
groupname = entry[group_attr['cn']]
|
||||
groups[groupname] = entry
|
||||
groups[name] = {}
|
||||
|
||||
groups[name]["members"] = [_ldap_path_extract(p, "uid") for p in infos.get("member", [])]
|
||||
|
||||
if full:
|
||||
groups[name]["permissions"] = [_ldap_path_extract(p, "cn") for p in infos.get("permission", [])]
|
||||
|
||||
if short:
|
||||
groups = groups.keys()
|
||||
|
||||
return {'groups': groups}
|
||||
|
||||
|
||||
@is_unit_operation([('groupname', 'user')])
|
||||
def user_group_add(operation_logger, groupname, gid=None, sync_perm=True):
|
||||
@is_unit_operation([('groupname', 'group')])
|
||||
def user_group_create(operation_logger, groupname, gid=None, primary_group=False, sync_perm=True):
|
||||
"""
|
||||
Create group
|
||||
|
||||
|
@ -565,8 +564,6 @@ def user_group_add(operation_logger, groupname, gid=None, sync_perm=True):
|
|||
from yunohost.permission import permission_sync_to_user
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
|
||||
operation_logger.start()
|
||||
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
# Validate uniqueness of groupname in LDAP
|
||||
|
@ -574,12 +571,12 @@ def user_group_add(operation_logger, groupname, gid=None, sync_perm=True):
|
|||
'cn': groupname
|
||||
}, base_dn='ou=groups,dc=yunohost,dc=org')
|
||||
if conflict:
|
||||
raise YunohostError('group_name_already_exist', name=groupname)
|
||||
raise YunohostError('group_already_exist', group=groupname)
|
||||
|
||||
# Validate uniqueness of groupname in system group
|
||||
all_existing_groupnames = {x.gr_name for x in grp.getgrall()}
|
||||
if groupname in all_existing_groupnames:
|
||||
raise YunohostError('system_groupname_exists')
|
||||
raise YunohostError('group_already_exist_on_system', group=groupname)
|
||||
|
||||
if not gid:
|
||||
# Get random GID
|
||||
|
@ -596,16 +593,30 @@ def user_group_add(operation_logger, groupname, gid=None, sync_perm=True):
|
|||
'gidNumber': gid,
|
||||
}
|
||||
|
||||
if ldap.add('cn=%s,ou=groups' % groupname, attr_dict):
|
||||
# Here we handle the creation of a primary group
|
||||
# We want to initialize this group to contain the corresponding user
|
||||
# (then we won't be able to add/remove any user in this group)
|
||||
if primary_group:
|
||||
attr_dict["member"] = ["uid=" + groupname + ",ou=users,dc=yunohost,dc=org"]
|
||||
|
||||
operation_logger.start()
|
||||
try:
|
||||
ldap.add('cn=%s,ou=groups' % groupname, attr_dict)
|
||||
except Exception as e:
|
||||
raise YunohostError('group_creation_failed', group=groupname, error=e)
|
||||
|
||||
if sync_perm:
|
||||
permission_sync_to_user()
|
||||
|
||||
if not primary_group:
|
||||
logger.success(m18n.n('group_created', group=groupname))
|
||||
if sync_perm:
|
||||
permission_sync_to_user()
|
||||
return {'name': groupname}
|
||||
else:
|
||||
logger.debug(m18n.n('group_created', group=groupname))
|
||||
|
||||
raise YunohostError('group_creation_failed', group=groupname)
|
||||
return {'name': groupname}
|
||||
|
||||
|
||||
@is_unit_operation([('groupname', 'user')])
|
||||
@is_unit_operation([('groupname', 'group')])
|
||||
def user_group_delete(operation_logger, groupname, force=False, sync_perm=True):
|
||||
"""
|
||||
Delete user
|
||||
|
@ -617,102 +628,109 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True):
|
|||
from yunohost.permission import permission_sync_to_user
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
|
||||
forbidden_groups = ["all_users", "admins"] + user_list(fields=['uid'])['users'].keys()
|
||||
if not force and groupname in forbidden_groups:
|
||||
raise YunohostError('group_deletion_not_allowed', group=groupname)
|
||||
existing_groups = user_group_list()['groups'].keys()
|
||||
if groupname not in existing_groups:
|
||||
raise YunohostError('group_unknown', group=groupname)
|
||||
|
||||
# Refuse to delete primary groups of a user (e.g. group 'sam' related to user 'sam')
|
||||
# without the force option...
|
||||
#
|
||||
# We also can't delete "all_users" because that's a special group...
|
||||
existing_users = user_list()['users'].keys()
|
||||
undeletable_groups = existing_users + ["all_users", "visitors"]
|
||||
if groupname in undeletable_groups and not force:
|
||||
raise YunohostError('group_cannot_be_deleted', group=groupname)
|
||||
|
||||
operation_logger.start()
|
||||
ldap = _get_ldap_interface()
|
||||
if not ldap.remove('cn=%s,ou=groups' % groupname):
|
||||
raise YunohostError('group_deletion_failed', group=groupname)
|
||||
try:
|
||||
ldap.remove('cn=%s,ou=groups' % groupname)
|
||||
except Exception as e:
|
||||
raise YunohostError('group_deletion_failed', group=groupname, error=e)
|
||||
|
||||
logger.success(m18n.n('group_deleted', group=groupname))
|
||||
if sync_perm:
|
||||
permission_sync_to_user()
|
||||
|
||||
if groupname not in existing_users:
|
||||
logger.success(m18n.n('group_deleted', group=groupname))
|
||||
else:
|
||||
logger.debug(m18n.n('group_deleted', group=groupname))
|
||||
|
||||
@is_unit_operation([('groupname', 'user')])
|
||||
def user_group_update(operation_logger, groupname, add_user=None, remove_user=None, force=False, sync_perm=True):
|
||||
|
||||
@is_unit_operation([('groupname', 'group')])
|
||||
def user_group_update(operation_logger, groupname, add=None, remove=None, force=False, sync_perm=True):
|
||||
"""
|
||||
Update user informations
|
||||
|
||||
Keyword argument:
|
||||
groupname -- Groupname to update
|
||||
add_user -- User to add in group
|
||||
remove_user -- User to remove in group
|
||||
add -- User(s) to add in group
|
||||
remove -- User(s) to remove in group
|
||||
|
||||
"""
|
||||
|
||||
from yunohost.permission import permission_sync_to_user
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
|
||||
if (groupname == 'all_users' or groupname == 'admins') and not force:
|
||||
raise YunohostError('edit_group_not_allowed', group=groupname)
|
||||
existing_users = user_list()['users'].keys()
|
||||
|
||||
ldap = _get_ldap_interface()
|
||||
# Refuse to edit a primary group of a user (e.g. group 'sam' related to user 'sam')
|
||||
# Those kind of group should only ever contain the user (e.g. sam) and only this one.
|
||||
# We also can't edit "all_users" without the force option because that's a special group...
|
||||
if not force:
|
||||
if groupname == "all_users":
|
||||
raise YunohostError('group_cannot_edit_all_users')
|
||||
elif groupname == "visitors":
|
||||
raise YunohostError('group_cannot_edit_visitors')
|
||||
elif groupname in existing_users:
|
||||
raise YunohostError('group_cannot_edit_primary_group', group=groupname)
|
||||
|
||||
# Populate group informations
|
||||
attrs_to_fetch = ['member']
|
||||
result = ldap.search(base='ou=groups,dc=yunohost,dc=org',
|
||||
filter='cn=' + groupname, attrs=attrs_to_fetch)
|
||||
if not result:
|
||||
raise YunohostError('group_unknown', group=groupname)
|
||||
group = result[0]
|
||||
# We extract the uid for each member of the group to keep a simple flat list of members
|
||||
current_group = user_group_info(groupname)["members"]
|
||||
new_group = copy.copy(current_group)
|
||||
|
||||
new_group_list = {'member': set(), 'memberUid': set()}
|
||||
if 'member' in group:
|
||||
new_group_list['member'] = set(group['member'])
|
||||
else:
|
||||
group['member'] = []
|
||||
if add:
|
||||
users_to_add = [add] if not isinstance(add, list) else add
|
||||
|
||||
existing_users = user_list(fields=['uid'])['users'].keys()
|
||||
|
||||
if add_user:
|
||||
if not isinstance(add_user, list):
|
||||
add_user = [add_user]
|
||||
|
||||
for user in add_user:
|
||||
for user in users_to_add:
|
||||
if user not in existing_users:
|
||||
raise YunohostError('user_unknown', user=user)
|
||||
|
||||
for user in add_user:
|
||||
userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org"
|
||||
if userDN in group['member']:
|
||||
logger.warning(m18n.n('user_already_in_group', user=user, group=groupname))
|
||||
new_group_list['member'].add(userDN)
|
||||
|
||||
if remove_user:
|
||||
if not isinstance(remove_user, list):
|
||||
remove_user = [remove_user]
|
||||
|
||||
for user in remove_user:
|
||||
if user == groupname:
|
||||
raise YunohostError('remove_user_of_group_not_allowed', user=user, group=groupname)
|
||||
|
||||
for user in remove_user:
|
||||
userDN = "uid=" + user + ",ou=users,dc=yunohost,dc=org"
|
||||
if 'member' in group and userDN in group['member']:
|
||||
new_group_list['member'].remove(userDN)
|
||||
if user in current_group:
|
||||
logger.warning(m18n.n('group_user_already_in_group', user=user, group=groupname))
|
||||
else:
|
||||
logger.warning(m18n.n('user_not_in_group', user=user, group=groupname))
|
||||
operation_logger.related_to.append(('user', user))
|
||||
|
||||
# Sychronise memberUid with member (to keep the posix group structure)
|
||||
# In posixgroup the main group of each user is only written in the gid number of the user
|
||||
for member in new_group_list['member']:
|
||||
member_Uid = member.split("=")[1].split(",")[0]
|
||||
# Don't add main user in the group.
|
||||
# Note that in the Unix system the main user of the group is linked by the gid in the user attribute.
|
||||
# So the main user need to be not in the memberUid list of his own group.
|
||||
if member_Uid != groupname:
|
||||
new_group_list['memberUid'].add(member_Uid)
|
||||
new_group += users_to_add
|
||||
|
||||
operation_logger.start()
|
||||
if remove:
|
||||
users_to_remove = [remove] if not isinstance(remove, list) else remove
|
||||
|
||||
if new_group_list['member'] != set(group['member']):
|
||||
if not ldap.update('cn=%s,ou=groups' % groupname, new_group_list):
|
||||
raise YunohostError('group_update_failed', group=groupname)
|
||||
for user in users_to_remove:
|
||||
if user not in current_group:
|
||||
logger.warning(m18n.n('group_user_not_in_group', user=user, group=groupname))
|
||||
else:
|
||||
operation_logger.related_to.append(('user', user))
|
||||
|
||||
# Remove users_to_remove from new_group
|
||||
# Kinda like a new_group -= users_to_remove
|
||||
new_group = [u for u in new_group if u not in users_to_remove]
|
||||
|
||||
new_group_dns = ["uid=" + user + ",ou=users,dc=yunohost,dc=org" for user in new_group]
|
||||
|
||||
if set(new_group) != set(current_group):
|
||||
operation_logger.start()
|
||||
ldap = _get_ldap_interface()
|
||||
try:
|
||||
ldap.update('cn=%s,ou=groups' % groupname, {"member": set(new_group_dns), "memberUid": set(new_group)})
|
||||
except Exception as e:
|
||||
raise YunohostError('group_update_failed', group=groupname, error=e)
|
||||
|
||||
if groupname != "all_users":
|
||||
logger.success(m18n.n('group_updated', group=groupname))
|
||||
else:
|
||||
logger.debug(m18n.n('group_updated', group=groupname))
|
||||
|
||||
logger.success(m18n.n('group_updated', group=groupname))
|
||||
if sync_perm:
|
||||
permission_sync_to_user()
|
||||
return user_group_info(groupname)
|
||||
|
@ -727,59 +745,46 @@ def user_group_info(groupname):
|
|||
|
||||
"""
|
||||
|
||||
from yunohost.utils.ldap import _get_ldap_interface
|
||||
from yunohost.utils.ldap import _get_ldap_interface, _ldap_path_extract
|
||||
ldap = _get_ldap_interface()
|
||||
|
||||
group_attrs = [
|
||||
'cn', 'member', 'permission'
|
||||
]
|
||||
result = ldap.search('ou=groups,dc=yunohost,dc=org', "cn=" + groupname, group_attrs)
|
||||
# Fetch info for this group
|
||||
result = ldap.search('ou=groups,dc=yunohost,dc=org',
|
||||
"cn=" + groupname,
|
||||
["cn", "member", "permission"])
|
||||
|
||||
if not result:
|
||||
raise YunohostError('group_unknown', group=groupname)
|
||||
|
||||
group = result[0]
|
||||
infos = result[0]
|
||||
|
||||
result_dict = {
|
||||
'groupname': group['cn'][0],
|
||||
'member': None
|
||||
# Format data
|
||||
|
||||
return {
|
||||
'members': [_ldap_path_extract(p, "uid") for p in infos.get("member", [])],
|
||||
'permissions': [_ldap_path_extract(p, "cn") for p in infos.get("permission", [])]
|
||||
}
|
||||
if 'member' in group:
|
||||
result_dict['member'] = {m.split("=")[1].split(",")[0] for m in group['member']}
|
||||
return result_dict
|
||||
|
||||
|
||||
#
|
||||
# Permission subcategory
|
||||
#
|
||||
|
||||
def user_permission_list(app=None, permission=None, username=None, group=None, sync_perm=True):
|
||||
def user_permission_list(short=False, full=False):
|
||||
import yunohost.permission
|
||||
return yunohost.permission.user_permission_list(app, permission, username, group)
|
||||
return yunohost.permission.user_permission_list(short, full)
|
||||
|
||||
|
||||
@is_unit_operation([('app', 'user')])
|
||||
def user_permission_add(operation_logger, app, permission="main", username=None, group=None, sync_perm=True):
|
||||
def user_permission_update(permission, add=None, remove=None, sync_perm=True):
|
||||
import yunohost.permission
|
||||
return yunohost.permission.user_permission_update(operation_logger, app, permission=permission,
|
||||
add_username=username, add_group=group,
|
||||
del_username=None, del_group=None,
|
||||
return yunohost.permission.user_permission_update(permission,
|
||||
add=add, remove=remove,
|
||||
sync_perm=sync_perm)
|
||||
|
||||
|
||||
@is_unit_operation([('app', 'user')])
|
||||
def user_permission_remove(operation_logger, app, permission="main", username=None, group=None, sync_perm=True):
|
||||
def user_permission_reset(permission, sync_perm=True):
|
||||
import yunohost.permission
|
||||
return yunohost.permission.user_permission_update(operation_logger, app, permission=permission,
|
||||
add_username=None, add_group=None,
|
||||
del_username=username, del_group=group,
|
||||
sync_perm=sync_perm)
|
||||
|
||||
|
||||
@is_unit_operation([('app', 'user')])
|
||||
def user_permission_clear(operation_logger, app, permission=None, sync_perm=True):
|
||||
import yunohost.permission
|
||||
return yunohost.permission.user_permission_clear(operation_logger, app, permission,
|
||||
return yunohost.permission.user_permission_reset(permission,
|
||||
sync_perm=sync_perm)
|
||||
|
||||
|
||||
|
|
|
@ -27,12 +27,14 @@ class YunohostError(MoulinetteError):
|
|||
|
||||
"""
|
||||
Yunohost base exception
|
||||
|
||||
|
||||
The (only?) main difference with MoulinetteError being that keys
|
||||
are translated via m18n.n (namespace) instead of m18n.g (global?)
|
||||
"""
|
||||
|
||||
def __init__(self, key, raw_msg=False, *args, **kwargs):
|
||||
self.key = key # Saving the key is useful for unit testing
|
||||
self.kwargs = kwargs # Saving the key is useful for unit testing
|
||||
if raw_msg:
|
||||
msg = key
|
||||
else:
|
||||
|
|
|
@ -40,6 +40,20 @@ def _get_ldap_interface():
|
|||
|
||||
return _ldap_interface
|
||||
|
||||
|
||||
# We regularly want to extract stuff like 'bar' in ldap path like
|
||||
# foo=bar,dn=users.example.org,ou=example.org,dc=org so this small helper allow
|
||||
# to do this without relying of dozens of mysterious string.split()[0]
|
||||
#
|
||||
# e.g. using _ldap_path_extract(path, "foo") on the previous example will
|
||||
# return bar
|
||||
|
||||
def _ldap_path_extract(path, info):
|
||||
for element in path.split(","):
|
||||
if element.startswith(info + "="):
|
||||
return element[len(info + "="):]
|
||||
|
||||
|
||||
# Add this to properly close / delete the ldap interface / authenticator
|
||||
# when Python exits ...
|
||||
# Otherwise there's a risk that some funky error appears at the very end
|
||||
|
|
|
@ -48,9 +48,9 @@ def get_network_interfaces():
|
|||
# Get network devices and their addresses (raw infos from 'ip addr')
|
||||
devices_raw = {}
|
||||
output = subprocess.check_output('ip addr show'.split())
|
||||
for d in re.split('^(?:[0-9]+: )', output, flags=re.MULTILINE):
|
||||
for d in re.split(r'^(?:[0-9]+: )', output, flags=re.MULTILINE):
|
||||
# Extract device name (1) and its addresses (2)
|
||||
m = re.match('([^\s@]+)(?:@[\S]+)?: (.*)', d, flags=re.DOTALL)
|
||||
m = re.match(r'([^\s@]+)(?:@[\S]+)?: (.*)', d, flags=re.DOTALL)
|
||||
if m:
|
||||
devices_raw[m.group(1)] = m.group(2)
|
||||
|
||||
|
@ -63,7 +63,7 @@ def get_network_interfaces():
|
|||
def get_gateway():
|
||||
|
||||
output = subprocess.check_output('ip route show'.split())
|
||||
m = re.search('default via (.*) dev ([a-z]+[0-9]?)', output)
|
||||
m = re.search(r'default via (.*) dev ([a-z]+[0-9]?)', output)
|
||||
if not m:
|
||||
return None
|
||||
|
||||
|
@ -71,9 +71,6 @@ def get_gateway():
|
|||
return addr.popitem()[1] if len(addr) == 1 else None
|
||||
|
||||
|
||||
#
|
||||
|
||||
|
||||
def _extract_inet(string, skip_netmask=False, skip_loopback=True):
|
||||
"""
|
||||
Extract IP addresses (v4 and/or v6) from a string limited to one
|
||||
|
@ -89,10 +86,10 @@ def _extract_inet(string, skip_netmask=False, skip_loopback=True):
|
|||
A dict of {protocol: address} with protocol one of 'ipv4' or 'ipv6'
|
||||
|
||||
"""
|
||||
ip4_pattern = '((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}'
|
||||
ip6_pattern = '(((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)'
|
||||
ip4_pattern += '/[0-9]{1,2})' if not skip_netmask else ')'
|
||||
ip6_pattern += '/[0-9]{1,3})' if not skip_netmask else ')'
|
||||
ip4_pattern = r'((25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}'
|
||||
ip6_pattern = r'(((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)'
|
||||
ip4_pattern += r'/[0-9]{1,2})' if not skip_netmask else ')'
|
||||
ip6_pattern += r'/[0-9]{1,3})' if not skip_netmask else ')'
|
||||
result = {}
|
||||
|
||||
for m in re.finditer(ip4_pattern, string):
|
||||
|
|
|
@ -33,36 +33,6 @@ logger = logging.getLogger('yunohost.utils.packages')
|
|||
|
||||
# Exceptions -----------------------------------------------------------------
|
||||
|
||||
class PackageException(Exception):
|
||||
|
||||
"""Base exception related to a package
|
||||
|
||||
Represent an exception related to the package named `pkgname`. If no
|
||||
`message` is provided, it will first try to use the translation key
|
||||
`message_key` if defined by the derived class. Otherwise, a standard
|
||||
message will be used.
|
||||
|
||||
"""
|
||||
message_key = 'package_unexpected_error'
|
||||
|
||||
def __init__(self, pkgname, message=None):
|
||||
super(PackageException, self).__init__(
|
||||
message or m18n.n(self.message_key, pkgname=pkgname))
|
||||
self.pkgname = pkgname
|
||||
|
||||
|
||||
class UnknownPackage(PackageException):
|
||||
|
||||
"""The package is not found in the cache."""
|
||||
message_key = 'package_unknown'
|
||||
|
||||
|
||||
class UninstalledPackage(PackageException):
|
||||
|
||||
"""The package is not installed."""
|
||||
message_key = 'package_not_installed'
|
||||
|
||||
|
||||
class InvalidSpecifier(ValueError):
|
||||
|
||||
"""An invalid specifier was found."""
|
||||
|
@ -402,43 +372,43 @@ def get_installed_version(*pkgnames, **kwargs):
|
|||
"""Get the installed version of package(s)
|
||||
|
||||
Retrieve one or more packages named `pkgnames` and return their installed
|
||||
version as a dict or as a string if only one is requested and `as_dict` is
|
||||
`False`. If `strict` is `True`, an exception will be raised if a package
|
||||
is unknown or not installed.
|
||||
version as a dict or as a string if only one is requested.
|
||||
|
||||
"""
|
||||
versions = OrderedDict()
|
||||
cache = apt.Cache()
|
||||
|
||||
# Retrieve options
|
||||
as_dict = kwargs.get('as_dict', False)
|
||||
strict = kwargs.get('strict', False)
|
||||
with_repo = kwargs.get('with_repo', False)
|
||||
|
||||
for pkgname in pkgnames:
|
||||
try:
|
||||
pkg = cache[pkgname]
|
||||
except KeyError:
|
||||
if strict:
|
||||
raise UnknownPackage(pkgname)
|
||||
logger.warning(m18n.n('package_unknown', pkgname=pkgname))
|
||||
if with_repo:
|
||||
versions[pkgname] = {
|
||||
"version": None,
|
||||
"repo": None,
|
||||
}
|
||||
else:
|
||||
versions[pkgname] = None
|
||||
continue
|
||||
|
||||
try:
|
||||
version = pkg.installed.version
|
||||
except AttributeError:
|
||||
if strict:
|
||||
raise UninstalledPackage(pkgname)
|
||||
version = None
|
||||
|
||||
try:
|
||||
# stable, testing, unstable
|
||||
repo = pkg.installed.origins[0].component
|
||||
except AttributeError:
|
||||
if strict:
|
||||
raise UninstalledPackage(pkgname)
|
||||
repo = ""
|
||||
|
||||
if repo == "now":
|
||||
repo = "local"
|
||||
|
||||
if with_repo:
|
||||
versions[pkgname] = {
|
||||
"version": version,
|
||||
|
@ -449,7 +419,7 @@ def get_installed_version(*pkgnames, **kwargs):
|
|||
else:
|
||||
versions[pkgname] = version
|
||||
|
||||
if len(pkgnames) == 1 and not as_dict:
|
||||
if len(pkgnames) == 1:
|
||||
return versions[pkgnames[0]]
|
||||
return versions
|
||||
|
||||
|
|
|
@ -5,23 +5,37 @@ import glob
|
|||
import json
|
||||
import yaml
|
||||
|
||||
ignore = [ "service_description_",
|
||||
"migration_description_",
|
||||
"global_settings_setting_",
|
||||
"password_too_simple_",
|
||||
"password_listed",
|
||||
"backup_method_",
|
||||
"backup_applying_method_",
|
||||
"confirm_app_install_",
|
||||
"log_",
|
||||
]
|
||||
|
||||
###############################################################################
|
||||
# Find used keys in python code #
|
||||
###############################################################################
|
||||
|
||||
# This regex matches « foo » in patterns like « m18n.n( "foo" »
|
||||
p = re.compile(r'm18n\.n\(\s*[\"\']([a-zA-Z1-9_]+)[\"\']')
|
||||
p1 = re.compile(r'm18n\.n\(\s*[\"\']([a-zA-Z0-9_]+)[\"\']')
|
||||
p2 = re.compile(r'YunohostError\([\'\"]([a-zA-Z0-9_]+)[\'\"]')
|
||||
|
||||
python_files = glob.glob("/vagrant/yunohost/src/yunohost/*.py")
|
||||
python_files.extend(glob.glob("/vagrant/yunohost/src/yunohost/utils/*.py"))
|
||||
python_files.append("/vagrant/yunohost/bin/yunohost")
|
||||
python_files = glob.glob("../src/yunohost/*.py")
|
||||
python_files.extend(glob.glob("../src/yunohost/utils/*.py"))
|
||||
python_files.extend(glob.glob("../src/yunohost/data_migrations/*.py"))
|
||||
python_files.append("../bin/yunohost")
|
||||
|
||||
python_keys = set()
|
||||
for python_file in python_files:
|
||||
with open(python_file) as f:
|
||||
keys_in_file = p.findall(f.read())
|
||||
for key in keys_in_file:
|
||||
python_keys.add(key)
|
||||
content = open(python_file).read()
|
||||
for match in p1.findall(content):
|
||||
python_keys.add(match)
|
||||
for match in p2.findall(content):
|
||||
python_keys.add(match)
|
||||
|
||||
###############################################################################
|
||||
# Find keys used in actionmap #
|
||||
|
@ -42,19 +56,20 @@ for _, category in actionmap.items():
|
|||
actionmap_keys.add(argument["extra"]["password"])
|
||||
if "ask" in argument["extra"]:
|
||||
actionmap_keys.add(argument["extra"]["ask"])
|
||||
if "comment" in argument["extra"]:
|
||||
actionmap_keys.add(argument["extra"]["comment"])
|
||||
if "pattern" in argument["extra"]:
|
||||
actionmap_keys.add(argument["extra"]["pattern"][1])
|
||||
if "help" in argument["extra"]:
|
||||
print argument["extra"]["help"]
|
||||
|
||||
# These keys are used but difficult to parse
|
||||
actionmap_keys.add("admin_password")
|
||||
|
||||
###############################################################################
|
||||
# Load en locale json keys #
|
||||
###############################################################################
|
||||
|
||||
en_locale_file = "/vagrant/yunohost/locales/en.json"
|
||||
en_locale_file = "../locales/en.json"
|
||||
with open(en_locale_file) as f:
|
||||
en_locale_json = json.loads(f.read())
|
||||
|
||||
|
@ -72,11 +87,15 @@ keys_defined_but_not_used = en_locale_keys.difference(used_keys)
|
|||
if len(keys_used_but_not_defined) != 0:
|
||||
print "> Error ! Those keys are used in some files but not defined :"
|
||||
for key in sorted(keys_used_but_not_defined):
|
||||
if any(key.startswith(i) for i in ignore):
|
||||
continue
|
||||
print " - %s" % key
|
||||
|
||||
if len(keys_defined_but_not_used) != 0:
|
||||
print "> Warning ! Those keys are defined but seems unused :"
|
||||
for key in sorted(keys_defined_but_not_used):
|
||||
if any(key.startswith(i) for i in ignore):
|
||||
continue
|
||||
print " - %s" % key
|
||||
|
||||
|
||||
|
|
18
tox.ini
Normal file
18
tox.ini
Normal file
|
@ -0,0 +1,18 @@
|
|||
[tox]
|
||||
envlist =
|
||||
py27
|
||||
lint
|
||||
skipdist = True
|
||||
|
||||
[testenv]
|
||||
skip_install=True
|
||||
deps =
|
||||
pytest >= 4.6.3, < 5.0
|
||||
pyyaml >= 5.1.2, < 6.0
|
||||
commands =
|
||||
pytest {posargs}
|
||||
|
||||
[testenv:lint]
|
||||
skip_install=True
|
||||
commands = flake8 src doc data tests
|
||||
deps = flake8
|
Loading…
Add table
Reference in a new issue