Merge branch 'testing' into stable

This commit is contained in:
opi 2017-02-02 11:23:14 +01:00
commit 93553f540c
44 changed files with 2191 additions and 427 deletions

5
.travis.yml Normal file
View file

@ -0,0 +1,5 @@
language: python
install: "pip install pytest pyyaml"
python:
- "2.7"
script: "py.test tests"

88
CONTRIBUTORS.md Normal file
View file

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

View file

@ -1,2 +1,42 @@
Please report issues here (no registration needed):
https://dev.yunohost.org/projects/yunohost/issues
# YunoHost core
- [YunoHost project website](https://yunohost.org)
This repository is the core of YunoHost code.
<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>
## Issues
- [Please report issues on YunoHost bugtracker](https://dev.yunohost.org/projects/yunohost/issues) (no registration needed).
## 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.
## 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.
## How does it works?
- 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).
## 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)
## License
As [other components of YunoHost core code](https://yunohost.org/#/faq_en), this repository is under GNU AGPL v.3 license.

View file

@ -66,6 +66,10 @@ def _parse_cli_args():
action='store_true', default=False,
help="Don't produce any output",
)
parser.add_argument('--timeout',
type=int, default=None,
help="Number of seconds before this command will timeout because it can't acquire the lock (meaning that another command is currently running), by default there is no timeout and the command will wait until it can get the lock",
)
parser.add_argument('--admin-password',
default=None, dest='password', metavar='PASSWORD',
help="The admin password to use to authenticate",
@ -209,6 +213,7 @@ if __name__ == '__main__':
ret = moulinette.cli(
_retrieve_namespaces(), args,
use_cache=opts.use_cache, output_as=opts.output_as,
password=opts.password, parser_kwargs={'top_parser': parser}
password=opts.password, parser_kwargs={'top_parser': parser},
timeout=opts.timeout,
)
sys.exit(ret)

View file

@ -305,6 +305,69 @@ domain:
- !!str ^[0-9]+$
- "pattern_positive_number"
### certificate_status()
cert-status:
action_help: List status of current certificates (all by default).
api: GET /domains/cert-status/<domain_list>
configuration:
authenticate: all
authenticator: ldap-anonymous
arguments:
domain_list:
help: Domains to check
nargs: "*"
--full:
help: Show more details
action: store_true
### certificate_install()
cert-install:
action_help: Install Let's Encrypt certificates for given domains (all by default).
api: POST /domains/cert-install/<domain_list>
configuration:
authenticate: all
authenticator: ldap-anonymous
arguments:
domain_list:
help: Domains for which to install the certificates
nargs: "*"
--force:
help: Install even if current certificate is not self-signed
action: store_true
--no-checks:
help: Does not perform any check that your domain seems correctly configured (DNS, reachability) before attempting to install. (Not recommended)
action: store_true
--self-signed:
help: Install self-signed certificate instead of Let's Encrypt
action: store_true
--staging:
help: Use the fake/staging Let's Encrypt certification authority. The new certificate won't actually be enabled - it is only intended to test the main steps of the procedure.
action: store_true
### certificate_renew()
cert-renew:
action_help: Renew the Let's Encrypt certificates for given domains (all by default).
api: POST /domains/cert-renew/<domain_list>
configuration:
authenticate: all
authenticator: ldap-anonymous
arguments:
domain_list:
help: Domains for which to renew the certificates
nargs: "*"
--force:
help: Ignore the validity threshold (30 days)
action: store_true
--email:
help: Send an email to root with logs if some renewing fails
action: store_true
--no-checks:
help: Does not perform any check that your domain seems correctly configured (DNS, reachability) before attempting to renew. (Not recommended)
action: store_true
--staging:
help: Use the fake/staging Let's Encrypt certification authority. The new certificate won't actually be enabled - it is only intended to test the main steps of the procedure.
action: store_true
### domain_info()
# info:
@ -436,6 +499,10 @@ app:
-a:
full: --args
help: Serialized arguments for app script (i.e. "domain=domain.tld&path=/path")
-n:
full: --no-remove-on-failure
help: Debug option to avoid removing the app on a failed installation
action: store_true
### app_remove() TODO: Write help
remove:
@ -1200,7 +1267,7 @@ tools:
### tools_maindomain()
maindomain:
action_help: Main domain change tool
action_help: Check the current main domain, or change it
api:
- GET /domains/main
- PUT /domains/main
@ -1208,12 +1275,9 @@ tools:
authenticate: all
lock: false
arguments:
-o:
full: --old-domain
extra:
pattern: *pattern_domain
-n:
full: --new-domain
help: Change the current main domain
extra:
pattern: *pattern_domain

View file

@ -17,7 +17,7 @@ ynh_backup() {
# validate arguments
[[ -e "${SRCPATH}" ]] || {
echo "Source path '${DESTPATH}' does not exist" >&2
echo "Source path '${SRCPATH}' does not exist" >&2
return 1
}

View file

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

View file

@ -102,6 +102,23 @@ do_post_regen() {
fi
sudo service slapd force-reload
# on slow hardware/vm this regen conf would exit before the admin user that
# is stored in ldap is available because ldap seems to slow to restart
# so we'll wait either until we are able to log as admin or until a timeout
# is reached
# we need to do this because the next hooks executed after this one during
# postinstall requires to run as admin thus breaking postinstall on slow
# hardware which mean yunohost can't be correctly installed on those hardware
# and this sucks
# wait a maximum time of 5 minutes
# yes, force-reload behave like a restart
number_of_wait=0
while ! sudo su admin -c '' && ((number_of_wait < 60))
do
sleep 5
((number_of_wait += 1))
done
}
FORCE=${2:-0}

View file

@ -26,11 +26,18 @@ do_pre_regen() {
's/^\(listen =\).*/\1 */' \
"${dovecot_dir}/dovecot.conf"
fi
mkdir -p "${dovecot_dir}/yunohost.d"
cp pre-ext.conf "${dovecot_dir}/yunohost.d"
cp post-ext.conf "${dovecot_dir}/yunohost.d"
}
do_post_regen() {
regen_conf_files=$1
sudo mkdir -p "/etc/dovecot/yunohost.d/pre-ext.d"
sudo mkdir -p "/etc/dovecot/yunohost.d/post-ext.d"
# create vmail user
id vmail > /dev/null 2>&1 \
|| sudo adduser --system --ingroup mail --uid 500 vmail

View file

@ -9,7 +9,9 @@ do_pre_regen() {
install -D -m 644 rmilter.conf \
"${pending_dir}/etc/rmilter.conf"
install -D -m 644 rmilter.socket \
# Remove old socket file (we stopped using it, since rspamd 1.3.1)
# Regen-conf system need an empty file to delete it
install -D -m 644 /dev/null \
"${pending_dir}/etc/systemd/system/rmilter.socket"
}
@ -37,17 +39,19 @@ do_post_regen() {
sudo chown _rmilter /etc/dkim/*.mail.key
sudo chmod 400 /etc/dkim/*.mail.key
# fix rmilter socket permission (postfix is chrooted in /var/spool/postfix )
sudo mkdir -p /var/spool/postfix/run/rmilter
sudo chown -R postfix:_rmilter /var/spool/postfix/run/rmilter
sudo chmod g+w /var/spool/postfix/run/rmilter
[ -z "$regen_conf_files" ] && exit 0
# reload systemd daemon
[[ "$regen_conf_files" =~ rmilter\.socket ]] && {
sudo systemctl -q daemon-reload
}
# ensure that the socket is listening and stop the service - it will be
# started again by the socket as needed
sudo systemctl -q start rmilter.socket
sudo systemctl -q stop rmilter.service 2>&1 || true
# Restart rmilter due to the rspamd update
# https://rspamd.com/announce/2016/08/01/rspamd-1.3.1.html
sudo systemctl -q restart rmilter.service
}
FORCE=${2:-0}

View file

@ -25,10 +25,9 @@ do_post_regen() {
sudo systemctl restart dovecot
}
# ensure that the socket is listening and stop the service - it will be
# started again by the socket as needed
sudo systemctl -q start rspamd.socket
sudo systemctl -q stop rspamd.service 2>&1 || true
# Restart rspamd due to the upgrade
# https://rspamd.com/announce/2016/08/01/rspamd-1.3.1.html
sudo systemctl -q restart rspamd.service
}
FORCE=${2:-0}

View file

@ -1,18 +1,48 @@
# 2.1.7: /etc/dovecot/dovecot.conf
# OS: Linux 3.2.0-3-686-pae i686 Debian wheezy/sid ext4
!include yunohost.d/pre-ext.conf
listen = *, ::
auth_mechanisms = plain login
login_greeting = Dovecot ready!!
mail_gid = 8
mail_home = /var/mail/%n
mail_location = maildir:/var/mail/%n
mail_uid = 500
protocols = imap sieve
mail_plugins = $mail_plugins quota
ssl = yes
ssl_cert = </etc/yunohost/certs/{{ main_domain }}/crt.pem
ssl_key = </etc/yunohost/certs/{{ main_domain }}/key.pem
ssl_protocols = !SSLv2 !SSLv3
passdb {
args = /etc/dovecot/dovecot-ldap.conf
driver = ldap
}
protocols = imap sieve
mail_plugins = $mail_plugins quota
userdb {
args = /etc/dovecot/dovecot-ldap.conf
driver = ldap
}
protocol imap {
imap_client_workarounds =
mail_plugins = $mail_plugins imap_quota antispam
}
protocol lda {
auth_socket_path = /var/run/dovecot/auth-master
mail_plugins = quota sieve
postmaster_address = postmaster@{{ main_domain }}
}
protocol sieve {
}
service auth {
unix_listener /var/spool/postfix/private/auth {
group = postfix
@ -26,26 +56,11 @@ service auth {
}
}
protocol sieve {
service quota-warning {
executable = script /usr/bin/quota-warning.sh
user = vmail
unix_listener quota-warning {
}
ssl = yes
ssl_cert = </etc/yunohost/certs/{{ main_domain }}/crt.pem
ssl_key = </etc/yunohost/certs/{{ main_domain }}/key.pem
ssl_protocols = !SSLv2 !SSLv3
userdb {
args = /etc/dovecot/dovecot-ldap.conf
driver = ldap
}
protocol imap {
imap_client_workarounds =
mail_plugins = $mail_plugins imap_quota antispam
}
protocol lda {
auth_socket_path = /var/run/dovecot/auth-master
mail_plugins = quota sieve
postmaster_address = postmaster@{{ main_domain }}
}
plugin {
@ -66,11 +81,6 @@ plugin {
antispam_pipe_program_notspam_arg = learn_ham
}
plugin {
autosubscribe = Trash
autosubscribe2 = Junk
}
plugin {
quota = maildir:User quota
quota_rule2 = SPAM:ignore
@ -83,9 +93,4 @@ plugin {
quota_warning3 = -storage=100%% quota-warning below %u # user is no longer over quota
}
service quota-warning {
executable = script /usr/bin/quota-warning.sh
user = vmail
unix_listener quota-warning {
}
}
!include yunohost.d/post-ext.conf

View file

@ -0,0 +1 @@
!include_try post-ext.d/*.conf

View file

@ -0,0 +1 @@
!include_try pre-ext.d/*.conf

View file

@ -581,4 +581,5 @@ enabled = true
port = http,https
protocol = tcp
filter = yunohost
logpath = /var/log/nginx/*.log
logpath = /var/log/nginx*/*error.log
maxretry = 6

View file

@ -14,7 +14,7 @@
# (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
# Values: TEXT
#
failregex = access.lua:[1-9]+: authenticate\(\): Connection failed for: .*, client: <HOST>
failregex = helpers.lua:[1-9]+: authenticate\(\): Connection failed for: .*, client: <HOST>
^<HOST> -.*\"POST /yunohost/api/login HTTP/1.1\" 401 22
# Option: ignoreregex

View file

@ -141,7 +141,7 @@ smtp_reply_filter = pcre:/etc/postfix/smtp_reply_filter
# Rmilter
milter_mail_macros = i {mail_addr} {client_addr} {client_name} {auth_authen}
milter_protocol = 6
smtpd_milters = inet:localhost:11000
smtpd_milters = unix:/run/rmilter/rmilter.sock
# Skip email without checking if milter has died
milter_default_action = accept

View file

@ -5,8 +5,7 @@
# pidfile - path to pid file
pidfile = /run/rmilter/rmilter.pid;
# rmilter is socket-activated under systemd
bind_socket = fd:3;
bind_socket = unix:/var/spool/postfix/run/rmilter/rmilter.sock;
# DKIM signing
dkim {

View file

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

138
debian/changelog vendored
View file

@ -1,3 +1,141 @@
yunohost (2.5.3.1) testing; urgency=low
* super quickfix release for a typo that break LE certificates
-- Laurent Peuch <cortex@worlddomination.be> Tue, 10 Jan 2017 02:58:56 +0100
yunohost (2.5.3) testing; urgency=low
Love:
* [enh][love] Add CONTRIBUTORS.md
LE:
* Check acme challenge conf exists in nginx when renewing cert
* Fix bad validity check..
Fix a situation where to domain for the LE cert can't be locally resolved:
* Adding check that domain is resolved locally for cert management
* Changing the way to check domain is locally resolved
Fix a situation where a cert could end up with bad perms for metronome:
* Attempt to fix missing perm for metronome in weird cases
Rspamd cannot be activate on socket anymore:
* [fix] new rspamd version replace rspamd.socket with rspamd.service
* [fix] Remove residual rmilter socket file
* [fix] Postfix can't access rmilter socket due to chroot
Various:
* fix fail2ban rules to take into account failed loggin on ssowat
* [fix] Ignore dyndns option is not needed with small domain
* [enh] add yaml syntax check in travis.yml
* [mod] autopep8 on all files that aren't concerned by a PR
* [fix] add timeout to fetchlist's wget
Thanks to all contributors: Aleks, Bram, ju, ljf, opi, zimo2001 and to the
people who are participating to the beta and giving us feedback <3
-- Laurent Peuch <cortex@worlddomination.be> Mon, 09 Jan 2017 18:38:30 +0100
yunohost (2.5.2) testing; urgency=low
LDAP admin user:
* [fix] wait for admin user to be available after a slapd regen-conf, this fix install on slow hardware/vps
Dovecot/emails:
* [enh] reorder dovecot main configuration so that it is easier to read and extend
* [enh] Allow for dovecot configuration extensions
* [fix] Can't get mailbos used space if dovecot is down
Backup:
* [fix] Need to create archives_path even for custom output directory
* Keep track of backups with custom directory using symlinks
Security:
* [fix] Improve dnssec key generation on low entropy devices
* [enh] Add haveged as dependency
Random broken app installed on slow hardware:
* [enh] List available domains when installing an app by CLI.
Translation:
* French by Jibec and Genma
* German by Philip Gatzka
* Hindi by Anmol
* Spanish by Juanu
Other fixes and improvements:
* [enh] remove timeout from cli interface
* [fix] [#662](https://dev.yunohost.org/issues/662): missing 'python-openssl' dependency for Let's Encrypt integration.
* [fix] --no-remove-on-failure for app install should behave as a flag.
* [fix] don't remove trailing char if it's not a slash
Thanks to all contributors: Aleks, alex, Anmol, Bram, Genma, jibec, ju,
Juanu, ljf, Moul, opi, Philip Gatzka and to the people who are participating
to the beta and giving us feedback <3
-- Laurent Peuch <cortex@worlddomination.be> Fri, 16 Dec 2016 00:49:08 +0100
yunohost (2.5.1) testing; urgency=low
* [fix] Raise error on malformed SSOwat persistent conf.
* [enh] Catch SSOwat persistent configuration write error.
* [fix] Write SSOwat configuration file only if needed.
* [enh] Display full exception error message.
* [enh] cli option to avoid removing an application on installation failure
* [mod] give instructions on how to solve the conf.json.persistant parsing error
* [fix] avoid random bug on post-install due to nscd cache
* [enh] Adding check that user is actually created + minor refactor of ldap/auth init
* [fix] Fix the way name of self-CA is determined
* [fix] Add missing dependency to nscd package #656
* [fix] Refactoring tools_maindomain and disabling removal of main domain to avoid breaking things
* [fix] Bracket in passwd from ynh_string_random
Thanks to all contributors: Aleks, Bram, ju, jibec, ljf, M5oul, opi
-- Laurent Peuch <cortex@worlddomination.be> Sun, 11 Dec 2016 15:26:21 +0100
yunohost (2.5.0) testing; urgency=low
* Certificate management integration (e.g. Let's Encrypt certificate install)
* [fix] Support git ynh app with submodules #533 (#174)
* [enh] display file path on file_not_exist error
* [mod] move a part of os.system calls to native shutil/os
* [fix] Can't restore app on a root domain
Miscellaneous
* Update backup.py
* [mod] autopep8
* [mod] trailing spaces
* [mod] pep8
* [mod] remove useless imports
* [mod] more pythonic and explicit tests with more verbose errors
* [fix] correctly handle all cases
* [mod] simplier condition
* [fix] uses https
* [mod] uses logger string concatenation api
* [mod] small opti, getting domain list can be slow
* [mod] pylint
* [mod] os.path.join
* [mod] remove useless assign
* [enh] include tracebak into error email
* [mod] remove the summary code concept and switch to code/verbose duet instead
* [mod] I only need to reload nginx, not restart it
* [mod] top level constants should be upper case (pep8)
* Check that the DNS A record matches the global IP now using dnspython and FDN's DNS
* Refactored the self-signed cert generation, some steps were overly complicated for no reason
* Using a single generic skipped regex for acme challenge in ssowat conf
* Adding an option to use the staging Let's Encrypt CA, sort of a dry-run
* [enh] Complete readme (#183)
* [fix] avoid reverse order log display on web admin
Thanks to all contributors: Aleks, Bram, JimboJoe, ljf, M5oul
Kudos to Aleks for leading the Let's Encrypt integration to YunoHost core \o/
-- opi <opi@zeropi.net> Thu, 01 Dec 2016 21:22:19 +0100
yunohost (2.4.2) stable; urgency=low
[ Laurent Peuch ]

5
debian/control vendored
View file

@ -11,13 +11,13 @@ Package: yunohost
Architecture: all
Depends: ${python:Depends}, ${misc:Depends}
, moulinette (>= 2.3.5.1)
, python-psutil, python-requests, python-dnspython
, python-psutil, python-requests, python-dnspython, python-openssl
, python-apt, python-miniupnpc
, glances
, dnsutils, bind9utils, unzip, git, curl, cron
, ca-certificates, netcat-openbsd, iproute
, mariadb-server | mysql-server, php5-mysql | php5-mysqlnd
, slapd, ldap-utils, sudo-ldap, libnss-ldapd
, slapd, ldap-utils, sudo-ldap, libnss-ldapd, nscd
, postfix-ldap, postfix-policyd-spf-perl, postfix-pcre, procmail
, dovecot-ldap, dovecot-lmtpd, dovecot-managesieved
, dovecot-antispam, fail2ban
@ -25,6 +25,7 @@ Depends: ${python:Depends}, ${misc:Depends}
, dnsmasq, openssl, avahi-daemon
, ssowat, metronome
, rspamd (>= 1.2.0), rmilter (>=1.7.0), redis-server, opendkim-tools
, haveged
Recommends: yunohost-admin
, openssh-server, ntp, inetutils-ping | iputils-ping
, bash-completion, rsyslog, etckeeper

1
locales/br.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -209,5 +209,14 @@
"yunohost_ca_creation_failed": "Zertifikatsstelle konnte nicht erstellt werden",
"yunohost_configured": "YunoHost wurde erfolgreich konfiguriert",
"yunohost_installing": "YunoHost wird installiert...",
"yunohost_not_installed": "Die YunoHost ist unvollständig. Bitte 'yunohost tools postinstall' ausführen."
"yunohost_not_installed": "Die YunoHost ist unvollständig. 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 Speicherplatz unter '{path:s}' verfügbar",
"backup_creation_failed": "Erzeugung des Backups fehlgeschlagen",
"service_conf_up_to_date": "Die Konfiguration für den Dienst '{service}' ist bereits aktuell",
"package_not_installed": "Paket '{pkgname}' ist nicht installiert",
"pattern_positive_number": "Muss eine positive Zahl sein",
"diagnostic_kernel_version_error": "Kann Kernelversion nicht abrufen: {error}",
"package_unexpected_error": "Ein unerwarteter Fehler trat bei der Verarbeitung des Pakets '{pkgname}' auf"
}

View file

@ -43,6 +43,7 @@
"backup_action_required": "You must specify something to save",
"backup_app_failed": "Unable to back up the app '{app:s}'",
"backup_archive_app_not_found": "App '{app:s}' not found in the backup archive",
"backup_archive_broken_link": "Unable to access backup archive (broken link to {path:s})",
"backup_archive_hook_not_exec": "Hook '{hook:s}' not executed in this backup",
"backup_archive_name_exists": "The backup's archive name already exists",
"backup_archive_name_unknown": "Unknown local backup archive named '{name:s}'",
@ -57,7 +58,7 @@
"backup_hook_unknown": "Backup hook '{hook:s}' unknown",
"backup_invalid_archive": "Invalid backup archive",
"backup_nothings_done": "There is nothing to save",
"backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders.",
"backup_output_directory_forbidden": "Forbidden output directory. Backups can't be created in /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var or /home/yunohost.backup/archives sub-folders",
"backup_output_directory_not_empty": "The output directory is not empty",
"backup_output_directory_required": "You must provide an output directory for the backup",
"backup_running_app_script": "Running backup script of app '{app:s}'...",
@ -71,7 +72,6 @@
"diagnostic_monitor_system_error": "Can't monitor system: {error}",
"diagnostic_no_apps": "No installed application",
"dnsmasq_isnt_installed": "dnsmasq does not seem to be installed, please run 'apt-get remove bind9 && apt-get install dnsmasq'",
"domain_cert_gen_failed": "Unable to generate certificate",
"domain_created": "The domain has been created",
"domain_creation_failed": "Unable to create domain",
"domain_deleted": "The domain has been deleted",
@ -80,11 +80,12 @@
"domain_dyndns_invalid": "Invalid domain to use with DynDNS",
"domain_dyndns_root_unknown": "Unknown DynDNS root domain",
"domain_exists": "Domain already exists",
"domain_uninstall_app_first": "One or more apps are installed on this domain. Please uninstall them before proceeding to domain removal.",
"domain_uninstall_app_first": "One or more apps are installed on this domain. Please uninstall them before proceeding to domain removal",
"domain_unknown": "Unknown domain",
"domain_zone_exists": "DNS zone file already exists",
"domain_zone_not_found": "DNS zone file not found for domain {:s}",
"done": "Done.",
"done": "Done",
"domains_available": "Available domains:",
"downloading": "Downloading...",
"dyndns_cron_installed": "The DynDNS cron job has been installed",
"dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job",
@ -103,7 +104,7 @@
"field_invalid": "Invalid field '{:s}'",
"firewall_reload_failed": "Unable to reload the firewall",
"firewall_reloaded": "The firewall has been reloaded",
"firewall_rules_cmd_failed": "Some firewall rules commands have failed. For more information, see the log.",
"firewall_rules_cmd_failed": "Some firewall rules commands have failed. For more information, see the log",
"format_datetime_short": "%m/%d/%Y %I:%M %p",
"hook_exec_failed": "Script execution failed: {path:s}",
"hook_exec_not_terminated": "Script execution hasnt terminated: {path:s}",
@ -111,13 +112,15 @@
"hook_name_unknown": "Unknown hook name '{name:s}'",
"installation_complete": "Installation complete",
"installation_failed": "Installation failed",
"ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it.",
"iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it.",
"ip6tables_unavailable": "You cannot play with ip6tables here. You are either in a container or your kernel does not support it",
"iptables_unavailable": "You cannot play with iptables here. You are either in a container or your kernel does not support it",
"ldap_initialized": "LDAP has been initialized",
"ldap_init_failed_to_create_admin": "LDAP initialization failed to create admin user",
"license_undefined": "undefined",
"mail_alias_remove_failed": "Unable to remove mail alias '{mail:s}'",
"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",
"monitor_disabled": "The server monitoring has been disabled",
@ -209,6 +212,8 @@
"service_unknown": "Unknown service '{service:s}'",
"ssowat_conf_generated": "The SSOwat configuration has been generated",
"ssowat_conf_updated": "The SSOwat configuration has been updated",
"ssowat_persistent_conf_read_error": "Error while reading SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax",
"ssowat_persistent_conf_write_error": "Error while saving SSOwat persistent configuration: {error:s}. Edit /etc/ssowat/conf.json.persistent file to fix the JSON syntax",
"system_upgraded": "The system has been upgraded",
"system_username_exists": "Username already exists in the system users",
"unbackup_app": "App '{app:s}' will not be saved",
@ -237,5 +242,31 @@
"yunohost_ca_creation_failed": "Unable to create certificate authority",
"yunohost_configured": "YunoHost has been configured",
"yunohost_installing": "Installing YunoHost...",
"yunohost_not_installed": "YunoHost is not or not correctly installed. Please execute 'yunohost tools postinstall'."
"yunohost_not_installed": "YunoHost is not or not correctly installed. Please execute 'yunohost tools postinstall'",
"domain_cert_gen_failed": "Unable to generate certificate",
"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_domain_unknown": "Unknown domain {domain:s}",
"certmanager_domain_cert_not_selfsigned": "The certificate for domain {domain:s} is not self-signed. Are you sure you want to replace it? (Use --force)",
"certmanager_certificate_fetching_or_enabling_failed": "Sounds like enabling the new certificate for {domain:s} failed somehow...",
"certmanager_attempt_to_renew_nonLE_cert": "The certificate for domain {domain:s} is not issued by Let's Encrypt. Cannot renew it automatically!",
"certmanager_attempt_to_renew_valid_cert": "The certificate for domain {domain:s} is not about to expire! Use --force to bypass",
"certmanager_domain_http_not_working": "It seems that the domain {domain:s} cannot be accessed through HTTP. Please check your DNS and nginx configuration is okay",
"certmanager_error_no_A_record": "No DNS 'A' record found for {domain:s}. You need to make your domain name point to your machine to be able to install a Let's Encrypt certificate! (If you know what you are doing, use --no-checks to disable those checks.)",
"certmanager_domain_dns_ip_differs_from_public_ip": "The DNS 'A' record for domain {domain:s} is different from this server IP. If you recently modified your A record, please wait for it to propagate (some DNS propagation checkers are available online). (If you know what you are doing, use --no-checks to disable those checks.)",
"certmanager_domain_not_resolved_locally": "The domain {domain:s} cannot be resolved from inside your Yunohost server. This might happen if you recently modified your DNS record. If so, please wait a few hours for it to propagate. If the issue persists, consider adding {domain:s} to /etc/hosts. (If you know what you are doing, use --no-checks to disable those checks.)",
"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_selfsigned": "Successfully installed a self-signed certificate for domain {domain:s}!",
"certmanager_cert_install_success": "Successfully installed Let's Encrypt certificate for domain {domain:s}!",
"certmanager_cert_renew_success": "Successfully renewed Let's Encrypt certificate for domain {domain:s}!",
"certmanager_old_letsencrypt_app_detected": "\nYunohost detected that the 'letsencrypt' app is installed, which conflits with the new built-in certificate management features in Yunohost. If you wish to use the new built-in features, please run the following commands to migrate your installation:\n\n yunohost app remove letsencrypt\n yunohost domain cert-install\n\nN.B.: this will attempt to re-install certificates for all domains with a Let's Encrypt certificate or self-signed certificate",
"certmanager_hit_rate_limit":"Too many certificates already issued for exact set of domains {domain:s} recently. Please try again later. See https://letsencrypt.org/docs/rate-limits/ for more details",
"certmanager_cert_signing_failed": "Signing the new certificate failed",
"certmanager_no_cert_file": "Unable to read certificate file for domain {domain:s} (file: {file:s})",
"certmanager_conflicting_nginx_file": "Unable to prepare domain for ACME challenge: the nginx configuration file {filepath:s} is conflicting and should be removed first",
"domain_cannot_remove_main": "Cannot remove main domain. Set a new main domain first",
"certmanager_self_ca_conf_file_not_found": "Configuration file not found for self-signing authority (file: {file:s})",
"certmanager_acme_not_configured_for_domain": "Certificate for domain {domain:s} does not appear to be correctly installed. Please run cert-install for this domain first.",
"certmanager_http_check_timeout" : "Timed out when server tried to contact itself through HTTP using public IP address (domain {domain:s} with ip {ip:s}). You may be experiencing hairpinning or the firewall/router ahead of your server is misconfigured.",
"certmanager_couldnt_fetch_intermediate_cert" : "Timed out when trying to fetch intermediate certificate from Let's Encrypt. Certificate installation/renewal aborted - please try again later.",
"certmanager_unable_to_parse_self_CA_name": "Unable to parse name of self-signing authority (file: {file:s})"
}

1
locales/eo.json Normal file
View file

@ -0,0 +1 @@
{}

View file

@ -1,12 +1,12 @@
{
"action_invalid": "Acción no válida '{action:s}'",
"action_invalid": "Acción no válida '{action:s} 1'",
"admin_password": "Contraseña administrativa",
"admin_password_change_failed": "No se pudo cambiar la contraseña",
"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} ya está instalada",
"app_argument_choice_invalid": "Opción no válida para el argumento '{name:s}', deber una de {choices:s}",
"app_argument_invalid": "Valor no válido para el argumento '{name:s}': {error:s}",
"app_argument_required": "Se requiere el argumento '{name:s}'",
"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",
"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_incompatible": "La aplicación no es compatible con su versión de YunoHost",
@ -15,9 +15,9 @@
"app_location_install_failed": "No se puede instalar la aplicación en esta localización",
"app_manifest_invalid": "El manifiesto de la aplicación no es válido",
"app_no_upgrade": "No hay aplicaciones para actualizar",
"app_not_correctly_installed": "La aplicación {app:s} parece estar incorrectamente instalada",
"app_not_installed": "{app:s} no está instalada",
"app_not_properly_removed": "La {app:s} no ha sido desinstalada correctamente",
"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_properly_removed": "La {app:s} 0 no ha sido desinstalada correctamente",
"app_package_need_update": "Es necesario actualizar el paquete de la aplicación 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",
@ -58,7 +58,7 @@
"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 /home/yunohost.backup.",
"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_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}'...",
@ -71,7 +71,7 @@
"diagnostic_monitor_network_error": "No se puede monitorizar la red: {error}",
"diagnostic_monitor_system_error": "No se puede monitorizar el sistema: {error}",
"diagnostic_no_apps": "Aplicación no instalada",
"dnsmasq_isnt_installed": "Parece que dnsmasq no está instalado, ejecuta 'apt-get remove bind9 && apt-get install dnsmasq'",
"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",
@ -84,11 +84,11 @@
"domain_uninstall_app_first": "Una o más aplicaciones están instaladas en este dominio. Debe desinstalarlas antes de eliminarlo.",
"domain_unknown": "Dominio desconocido",
"domain_zone_exists": "El archivo de zona del DNS ya existe",
"domain_zone_not_found": "No se ha encontrado el archivo de zona DNS para el dominio [:s]",
"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 del cron DynDNS",
"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",
@ -113,16 +113,16 @@
"hook_list_by_invalid": "Propiedad no válida para listar por hook",
"hook_name_unknown": "Nombre de hook desconocido '{name:s}'",
"installation_complete": "Instalación finalizada",
"installation_failed": "No pudo realizar 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": "LDAP iniciado",
"installation_failed": "No se pudo realizar 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",
"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": "El dominio principal ha sido cambiado",
"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",
@ -130,13 +130,13 @@
"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 ese período",
"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 iniciada",
"mysql_db_initialized": "La base de datos MySQL ha sido 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 en su red",
"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",
"new_domain_required": "Debe proporcionar el nuevo dominio principal",
"no_appslist_found": "No se ha encontrado ninguna lista de aplicaciones",
@ -145,13 +145,13 @@
"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",
"package_unexpected_error": "Un error inesperado procesando el paquete '{pkgname}'",
"package_unexpected_error": "Ha ocurrido un error inesperado procesando el paquete '{pkgname}'",
"package_unknown": "Paquete desconocido '{pkgname}'",
"packages_no_upgrade": "No hay paquetes que actualizar",
"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 borrar la ruta {:s}",
"pattern_backup_archive_name": "Debe que ser un nombre de archivo válido, solo se admiten caracteres alfanuméricos y los guiones -_",
"path_removal_failed": "No se pudo eliminar la ruta {:s}",
"pattern_backup_archive_name": "Debe ser un nombre de archivo válido, solo se admiten caracteres alfanuméricos y los guiones -_",
"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_firstname": "Debe ser un nombre válido",
@ -180,7 +180,7 @@
"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 iniciado",
"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}'",
"service_conf_file_backed_up": "Se ha realizado una copia de seguridad del archivo de configuración '{conf}' en '{backup}'",
@ -193,15 +193,15 @@
"service_conf_file_updated": "El archivo de configuración '{conf}' ha sido actualizado",
"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}'",
"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_regenconf_dry_pending_applying": "Comprobando configuración que podría haber sido aplicada al servicio '{service}'...",
"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 para el servicio '{service}'...",
"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}'",
@ -219,7 +219,7 @@
"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é APT",
"update_cache_failed": "No se pudo actualizar la caché de APT",
"updating_apt_cache": "Actualizando lista de paquetes disponibles...",
"upgrade_complete": "Actualización finalizada",
"upgrading_packages": "Actualizando paquetes...",
@ -240,5 +240,31 @@
"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'."
"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",
"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_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_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_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)",
"domains_available": "Dominios instalados:"
}

View file

@ -59,7 +59,7 @@
"backup_hook_unknown": "Script de sauvegarde « {hook:s} » inconnu",
"backup_invalid_archive": "Archive de sauvegarde incorrecte",
"backup_nothings_done": "Il n'y a rien à sauvegarder",
"backup_output_directory_forbidden": "Dossier de sortie interdit. Les sauvegardes ne peuvent être créées dans les dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives.",
"backup_output_directory_forbidden": "Dossier de destination interdit. Les sauvegardes ne peuvent être créées dans les dossiers /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var ou /home/yunohost.backup/archives",
"backup_output_directory_not_empty": "Le dossier de sortie n'est pas vide",
"backup_output_directory_required": "Vous devez spécifier un dossier de sortie pour la sauvegarde",
"backup_running_app_script": "Lancement du script de sauvegarde de l'application « {app:s} »...",
@ -82,11 +82,11 @@
"domain_dyndns_invalid": "Domaine incorrect pour un usage avec DynDNS",
"domain_dyndns_root_unknown": "Domaine DynDNS principal inconnu",
"domain_exists": "Le domaine existe déjà",
"domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d'abord les désinstaller avant de supprimer ce domaine.",
"domain_uninstall_app_first": "Une ou plusieurs applications sont installées sur ce domaine. Veuillez d'abord les désinstaller avant de supprimer ce domaine",
"domain_unknown": "Domaine inconnu",
"domain_zone_exists": "Le fichier de zone DNS existe déjà",
"domain_zone_not_found": "Fichier de zone DNS introuvable pour le domaine {:s}",
"done": "Terminé.",
"done": "Terminé",
"downloading": "Téléchargement...",
"dyndns_cron_installed": "La tâche cron pour le domaine DynDNS a été installée",
"dyndns_cron_remove_failed": "Impossible d'enlever la tâche cron pour le domaine DynDNS",
@ -105,23 +105,23 @@
"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_rules_cmd_failed": "Certaines règles du pare-feu n'ont pas pu être appliquées. Pour plus d'informations, consultez le journal",
"format_datetime_short": "%d/%m/%Y %H:%M",
"hook_argument_missing": "Argument manquant : '{:s}'",
"hook_choice_invalid": "Choix incorrect : '{:s}'",
"hook_exec_failed": "Échec de l'exécution du script « {path:s} »",
"hook_exec_not_terminated": "L'exécution du script « {path:s} » ne s'est pas terminée",
"hook_list_by_invalid": "Propriété pour lister les scripts incorrecte",
"hook_list_by_invalid": "Propriété pour lister les scripts incorrects",
"hook_name_unknown": "Nom de script « {name:s} » inconnu",
"installation_complete": "Installation terminée",
"installation_failed": "Échec de l'installation",
"ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes sûrement dans un conteneur, ou alors votre noyau ne le supporte pas.",
"iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes sûrement dans un conteneur, autrement votre noyau ne le supporte pas.",
"ip6tables_unavailable": "Vous ne pouvez pas jouer avec ip6tables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le supporte pas",
"iptables_unavailable": "Vous ne pouvez pas jouer avec iptables ici. Vous êtes soit dans un conteneur, soit votre noyau ne le supporte pas",
"ldap_initialized": "L'annuaire LDAP a été initialisé",
"license_undefined": "indéfinie",
"mail_alias_remove_failed": "Impossible de supprimer l'adresse courriel supplémentaire « {mail:s} »",
"mail_domain_unknown": "Le domaine « {domain:s} » de l'adresse courriel est inconnu",
"mail_forward_remove_failed": "Impossible de supprimer l'adresse courriel de transfert « {mail:s} »",
"mail_alias_remove_failed": "Impossible de supprimer l'alias courriel « {mail:s} »",
"mail_domain_unknown": "Le domaine « {domain:s} » du courriel est inconnu",
"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é",
@ -155,7 +155,7 @@
"path_removal_failed": "Impossible de supprimer le chemin {:s}",
"pattern_backup_archive_name": "Doit être un nom de fichier valide composé de caractères alphanumérique et -_. uniquement",
"pattern_domain": "Doit être un nom de domaine valide (ex : mon-domaine.org)",
"pattern_email": "Doit être une adresse courriel valide (ex. : someone@domain.org)",
"pattern_email": "Doit être une adresse courriel valide (ex. : pseudo@domain.org)",
"pattern_firstname": "Doit être un prénom valide",
"pattern_lastname": "Doit être un nom valide",
"pattern_listname": "Doit être composé uniquement de caractères alphanumériques et de tirets bas",
@ -248,5 +248,36 @@
"yunohost_ca_creation_failed": "Impossible de créer l'autorité de certification",
"yunohost_configured": "YunoHost a été configuré",
"yunohost_installing": "Installation de YunoHost...",
"yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter « yunohost tools postinstall »."
"yunohost_not_installed": "YunoHost n'est pas ou pas correctement installé. Veuillez exécuter « yunohost tools postinstall »",
"certmanager_attempt_to_replace_valid_cert": "Vous êtes en train de remplacer un certificat correct et valide pour le domaine {domain:s} ! (Utilisez --force pour contourner)",
"certmanager_domain_unknown": "Domaine inconnu {domain:s}",
"certmanager_domain_cert_not_selfsigned": "Le certificat du domaine {domain:s} nest pas auto-signé. Voulez-vous vraiment le remplacer ? (Utilisez --force)",
"certmanager_certificate_fetching_or_enabling_failed": "Il semble que l'activation du nouveau certificat pour {domain:s} a échoué…",
"certmanager_attempt_to_renew_nonLE_cert": "Le certificat pour le domaine {domain:s} nest pas fourni par Lets Encrypt. Impossible de le renouveler automatiquement !",
"certmanager_attempt_to_renew_valid_cert": "Le certificat pour le domaine {domain:s} est sur le point dexpirer ! Utilisez --force pour contourner",
"certmanager_domain_http_not_working": "Il semble que le domaine {domain:s} nest pas accessible via HTTP. Veuillez vérifier que vos configuration DNS et nginx sont correctes",
"certmanager_error_no_A_record": "Aucun enregistrement DNS « A » na été trouvé pour {domain:s}. De devez faire pointer votre nom de domaine vers votre machine pour être capable dinstaller un certificat Lets 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": "Lenregistrement DNS « A » du domaine {domain:s} est différent de ladresse IP de ce serveur. Si vous avez modifié récemment votre enregistrement « A », veuillez attendre sa propagation (quelques vérificateur de propagation 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 sest mal passé lors de la tentative douverture du certificat actuel pour le domaine {domain:s} (fichier : {file:s}), cause : {reason:s}",
"certmanager_cert_install_success_selfsigned": "Installation avec succès dun certificat auto-signé pour le domaine {domain:s} !",
"certmanager_cert_install_success": "Installation avec succès dun certificat Lets Encrypt pour le domaine {domain:s} !",
"certmanager_cert_renew_success": "Renouvellement avec succès dun certificat Lets Encrypt pour le domaine {domain:s} !",
"certmanager_old_letsencrypt_app_detected": "\nYunoHost a détecté que lapplication « 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_no_cert_file": "Impossible de lire le fichier de certificat pour le domaine {domain:s} (fichier : {file:s})",
"certmanager_conflicting_nginx_file": "Impossible de préparer le domaine pour de défi ACME : le fichier de configuration nginx {filepath:s} est en conflit et doit être retiré au préalable",
"certmanager_hit_rate_limit": "Trop de certificats ont déjà été demandés récemment pour cet ensemble précis de domaines {domain:s}. Veuillez réessayer plus tard. Lisez https://letsencrypt.org/docs/rate-limits/ pour obtenir plus de détails",
"ldap_init_failed_to_create_admin": "Linitialisation de LDAP na pas réussi à créer lutilisateur 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",
"domain_cannot_remove_main": "Impossible de retirer le domaine principal. Définissez un nouveau domaine principal au préalable.",
"certmanager_self_ca_conf_file_not_found": "Le fichier de configuration pour lautorité du certificat auto-signé est introuvable (fichier : {file:s})",
"certmanager_unable_to_parse_self_CA_name": "Impossible danalyser le nom de lautorité du certificat auto-signé (fichier : {file:s})",
"mailbox_used_space_dovecot_down": "Le service de mail Dovecot doit être démarré, si vous souhaitez voir l'espace disque occupé par la messagerie",
"domains_available": "Domaines disponibles :",
"backup_archive_broken_link": "Impossible d'accéder à l'archive de la sauvegarde (lien cassé à {path:s})",
"certmanager_acme_not_configured_for_domain": "Le certificat du domaine {domain:s} ne semble pas être correctement installé. Veuillez préalablement exécuter cert-install pour ce domaine.",
"certmanager_domain_not_resolved_locally": "Le domaine {domain:s} ne peut être déterminé depuis votre serveur YunoHost. Cela peut arriver si vous avez récemment modifié votre enregistrement DNS. Auquel cas, merci dattendre quelques heures quil se propage. Si le problème persiste, envisager dajouter {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 lors de la tentative du serveur de se contacter via HTTP en utilisant son adresse IP publique (domaine {domain:s} avec lIP {ip:s}). Vous rencontrez peut-être un problème dhairpinning 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 Lets Encrypt. Linstallation/le renouvellement du certificat a été interrompu - veuillez réessayer prochainement."
}

View file

@ -1 +1,81 @@
{}
{
"action_invalid": "अवैध कार्रवाई '{action:s}'",
"admin_password": "व्यवस्थापक पासवर्ड",
"admin_password_change_failed": "पासवर्ड बदलने में असमर्थ",
"admin_password_changed": "व्यवस्थापक पासवर्ड बदल दिया गया है",
"app_already_installed": "'{app:s}' पहले से ही इंस्टाल्ड है",
"app_argument_choice_invalid": "गलत तर्क का चयन किया गया '{name:s}' , तर्क इन विकल्पों में से होने चाहिए {choices:s}",
"app_argument_invalid": "तर्क के लिए अमान्य मान '{name:s}': {error:s}",
"app_argument_required": "तर्क '{name:s}' की आवश्यकता है",
"app_extraction_failed": "इन्सटाल्ड फ़ाइलों को निकालने में असमर्थ",
"app_id_invalid": "अवैध एप्लिकेशन id",
"app_incompatible": "यह एप्लिकेशन युनोहोस्ट की इस वर्जन के लिए नहीं है",
"app_install_files_invalid": "फाइलों की अमान्य स्थापना",
"app_location_already_used": "इस लोकेशन पे पहले से ही कोई एप्लीकेशन इन्सटाल्ड है",
"app_location_install_failed": "इस लोकेशन पे एप्लीकेशन इंस्टाल करने में असमर्थ",
"app_manifest_invalid": "एप्लीकेशन का मैनिफेस्ट अमान्य",
"app_no_upgrade": "कोई भी एप्लीकेशन को अपडेट की जरूरत नहीं",
"app_not_correctly_installed": "{app:s} ठीक ढंग से इनस्टॉल नहीं हुई",
"app_not_installed": "{app:s} इनस्टॉल नहीं हुई",
"app_not_properly_removed": "{app:s} ठीक ढंग से नहीं अनइन्सटॉल की गई",
"app_package_need_update": "इस एप्लीकेशन पैकेज को युनोहोस्ट के नए बदलावों/गाइडलिनेज़ के कारण उपडटेशन की जरूरत",
"app_removed": "{app:s} को अनइन्सटॉल कर दिया गया",
"app_requirements_checking": "जरूरी पैकेजेज़ की जाँच हो रही है ....",
"app_requirements_failed": "आवश्यकताओं को पूरा करने में असमर्थ: {error}",
"app_requirements_unmeet": "आवश्यकताए पूरी नहीं हो सकी, पैकेज {pkgname}({version})यह होना चाहिए {spec}",
"app_sources_fetch_failed": "सोर्स फाइल्स प्राप्त करने में असमर्थ",
"app_unknown": "अनजान एप्लीकेशन",
"app_unsupported_remote_type": "एप्लीकेशन के लिए उन्सुपपोर्टेड रिमोट टाइप इस्तेमाल किया गया",
"app_upgrade_failed": "{app:s} अपडेट करने में असमर्थ",
"app_upgraded": "{app:s} अपडेट हो गयी हैं",
"appslist_fetched": "एप्लीकेशन की सूचि अपडेट हो गयी",
"appslist_removed": "एप्लीकेशन की सूचि निकल दी गयी है",
"appslist_retrieve_error": "दूरस्थ एप्लिकेशन सूची प्राप्त करने में असमर्थ",
"appslist_unknown": "अनजान एप्लिकेशन सूची",
"ask_current_admin_password": "वर्तमान व्यवस्थापक पासवर्ड",
"ask_email": "ईमेल का पता",
"ask_firstname": "नाम",
"ask_lastname": "अंतिम नाम",
"ask_list_to_remove": "सूचि जिसको हटाना है",
"ask_main_domain": "मुख्य डोमेन",
"ask_new_admin_password": "नया व्यवस्थापक पासवर्ड",
"ask_password": "पासवर्ड",
"backup_action_required": "आप को सेव करने के लिए कुछ लिखना होगा",
"backup_app_failed": "एप्लीकेशन का बैकअप करने में असमर्थ '{app:s}'",
"backup_archive_app_not_found": "'{app:s}' बैकअप आरचिव में नहीं मिला",
"backup_archive_hook_not_exec": "हुक '{hook:s}' इस बैकअप में एक्सेक्युट नहीं किया गया",
"backup_archive_name_exists": "इस बैकअप आरचिव का नाम पहले से ही मौजूद है",
"backup_archive_name_unknown": "'{name:s}' इस नाम की लोकल बैकअप आरचिव मौजूद नहीं",
"backup_archive_open_failed": "बैकअप आरचिव को खोलने में असमर्थ",
"backup_cleaning_failed": "टेम्पोरेरी बैकअप डायरेक्टरी को उड़ने में असमर्थ",
"backup_created": "बैकअप सफलतापूर्वक किया गया",
"backup_creating_archive": "बैकअप आरचिव बनाई जा रही है ...",
"backup_creation_failed": "बैकअप बनाने में विफल",
"backup_delete_error": "'{path:s}' डिलीट करने में असमर्थ",
"backup_deleted": "इस बैकअप को डिलीट दिया गया है",
"backup_extracting_archive": "बैकअप आरचिव को एक्सट्रेक्ट किया जा रहा है ...",
"backup_hook_unknown": "'{hook:s}' यह बैकअप हुक नहीं मिला",
"backup_invalid_archive": "अवैध बैकअप आरचिव",
"backup_nothings_done": "सेव करने के लिए कुछ नहीं",
"backup_output_directory_forbidden": "निषिद्ध आउटपुट डायरेक्टरी। निम्न दिए गए डायरेक्टरी में बैकअप नहीं बन सकता /bin, /boot, /dev, /etc, /lib, /root, /run, /sbin, /sys, /usr, /var और /home/yunohost.backup/archives के सब-फोल्डर।",
"backup_output_directory_not_empty": "आउटपुट डायरेक्टरी खाली नहीं है",
"backup_output_directory_required": "बैकअप करने के लिए आउट पुट डायरेक्टरी की आवश्यकता है",
"backup_running_app_script": "'{app:s}' एप्लीकेशन की बैकअप स्क्रिप्ट चल रही है...",
"backup_running_hooks": "बैकअप हुक्स चल रहे है...",
"custom_app_url_required": "आप को अपनी कस्टम एप्लिकेशन '{app:s}' को अपग्रेड करने के लिए यूआरएल(URL) देने की आवश्यकता है",
"custom_appslist_name_required": "आप को अपनी कस्टम एप्लीकेशन के लिए नाम देने की आवश्यकता है",
"diagnostic_debian_version_error": "डेबियन वर्जन प्राप्त करने में असफलता {error}",
"diagnostic_kernel_version_error": "कर्नेल वर्जन प्राप्त नहीं की जा पा रही : {error}",
"diagnostic_monitor_disk_error": "डिस्क की मॉनिटरिंग नहीं की जा पा रही: {error}",
"diagnostic_monitor_network_error": "नेटवर्क की मॉनिटरिंग नहीं की जा पा रही: {error}",
"diagnostic_monitor_system_error": "सिस्टम की मॉनिटरिंग नहीं की जा पा रही: {error}",
"diagnostic_no_apps": "कोई एप्लीकेशन इन्सटाल्ड नहीं है",
"dnsmasq_isnt_installed": "dnsmasq इन्सटाल्ड नहीं लगता,इनस्टॉल करने के लिए किप्या ये कमांड चलाये 'apt-get remove bind9 && apt-get install dnsmasq'",
"domain_cert_gen_failed": "सर्टिफिकेट उत्पन करने में असमर्थ",
"domain_created": "डोमेन बनाया गया",
"domain_creation_failed": "डोमेन बनाने में असमर्थ",
"domain_deleted": "डोमेन डिलीट कर दिया गया है",
"domain_deletion_failed": "डोमेन डिलीट करने में असमर्थ",
"domain_dyndns_already_subscribed": "DynDNS डोमेन पहले ही सब्स्क्राइड है",
"domain_dyndns_invalid": "DynDNS के साथ इनवैलिड डोमिन इस्तेमाल किया गया"
}

View file

@ -24,10 +24,8 @@
Manage apps
"""
import os
import sys
import json
import shutil
import stat
import yaml
import time
import re
@ -101,7 +99,7 @@ def app_fetchlist(url=None, name=None):
m18n.n('custom_appslist_name_required'))
list_file = '%s/%s.json' % (repo_path, name)
if os.system('wget "%s" -O "%s.tmp"' % (url, list_file)) != 0:
if os.system('wget --timeout=30 "%s" -O "%s.tmp"' % (url, list_file)) != 0:
os.remove('%s.tmp' % list_file)
raise MoulinetteError(errno.EBADR, m18n.n('appslist_retrieve_error'))
@ -440,7 +438,7 @@ def app_upgrade(auth, app=[], url=None, file=None):
logger.success(m18n.n('upgrade_complete'))
def app_install(auth, app, label=None, args=None):
def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
"""
Install apps
@ -448,6 +446,7 @@ def app_install(auth, app, label=None, args=None):
app -- Name, local path or git URL of the app to install
label -- Custom name for the app
args -- Serialize arguments for app installation
no_remove_on_failure -- Debug option to avoid removing the app on a failed installation
"""
from yunohost.hook import hook_add, hook_remove, hook_exec
@ -541,6 +540,7 @@ def app_install(auth, app, label=None, args=None):
logger.exception(m18n.n('unexpected_error'))
finally:
if install_retcode != 0:
if not no_remove_on_failure:
# Setup environment for remove script
env_dict_remove = {}
env_dict_remove["YNH_APP_ID"] = app_id
@ -811,6 +811,9 @@ def app_makedefault(auth, app, domain=None):
try:
with open('/etc/ssowat/conf.json.persistent') as json_conf:
ssowat_conf = json.loads(str(json_conf.read()))
except ValueError as e:
raise MoulinetteError(errno.EINVAL,
m18n.n('ssowat_persistent_conf_read_error', error=e.strerror))
except IOError:
ssowat_conf = {}
@ -819,8 +822,13 @@ def app_makedefault(auth, app, domain=None):
ssowat_conf['redirected_urls'][domain +'/'] = app_domain + app_path
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 MoulinetteError(errno.EPERM,
m18n.n('ssowat_persistent_conf_write_error', error=e.strerror))
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
@ -909,10 +917,6 @@ def app_checkurl(auth, url, app=None):
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
if domain in apps_map:
# Domain already has apps on sub path
if path == '/':
raise MoulinetteError(errno.EPERM,
m18n.n('app_location_install_failed'))
# Loop through apps
for p, a in apps_map[domain].items():
# Skip requested app checking
@ -922,7 +926,7 @@ def app_checkurl(auth, url, app=None):
if path == p:
raise MoulinetteError(errno.EINVAL,
m18n.n('app_location_already_used'))
elif path.startswith(p):
elif path.startswith(p) or p.startswith(path):
raise MoulinetteError(errno.EPERM,
m18n.n('app_location_install_failed'))
@ -991,7 +995,6 @@ def app_ssowatconf(auth):
redirected_regex = { main_domain +'/yunohost[\/]?$': 'https://'+ main_domain +'/yunohost/sso/' }
redirected_urls ={}
apps = {}
try:
apps_list = app_list()['apps']
except:
@ -1008,19 +1011,19 @@ def app_ssowatconf(auth):
for item in _get_setting(app_settings, 'skipped_uris'):
if item[-1:] == '/':
item = item[:-1]
skipped_urls.append(app_settings['domain'] + app_settings['path'][:-1] + item)
skipped_urls.append(app_settings['domain'] + app_settings['path'].rstrip('/') + item)
for item in _get_setting(app_settings, 'skipped_regex'):
skipped_regex.append(item)
for item in _get_setting(app_settings, 'unprotected_uris'):
if item[-1:] == '/':
item = item[:-1]
unprotected_urls.append(app_settings['domain'] + app_settings['path'][:-1] + item)
unprotected_urls.append(app_settings['domain'] + app_settings['path'].rstrip('/') + item)
for item in _get_setting(app_settings, 'unprotected_regex'):
unprotected_regex.append(item)
for item in _get_setting(app_settings, 'protected_uris'):
if item[-1:] == '/':
item = item[:-1]
protected_urls.append(app_settings['domain'] + app_settings['path'][:-1] + item)
protected_urls.append(app_settings['domain'] + app_settings['path'].rstrip('/') + item)
for item in _get_setting(app_settings, 'protected_regex'):
protected_regex.append(item)
if 'redirected_urls' in app_settings:
@ -1031,6 +1034,9 @@ def app_ssowatconf(auth):
for domain in domains:
skipped_urls.extend([domain + '/yunohost/admin', domain + '/yunohost/api'])
# Authorize ACME challenge url
skipped_regex.append("^[^/]*/%.well%-known/acme%-challenge/.*$")
conf_dict = {
'portal_domain': main_domain,
'portal_path': '/yunohost/sso/',
@ -1259,8 +1265,13 @@ def _fetch_app_from_git(app):
url = url[:tree_index]
branch = app[tree_index+6:]
try:
# We use currently git 2.1 so we can't use --shallow-submodules
# option. When git will be in 2.9 (with the new debian version)
# we will be able to use it. Without this option all the history
# of the submodules repo is downloaded.
subprocess.check_call([
'git', 'clone', '--depth=1', url, extracted_app_folder])
'git', 'clone', '--depth=1', '--recursive', url,
extracted_app_folder])
subprocess.check_call([
'git', 'reset', '--hard', branch
], cwd=extracted_app_folder)
@ -1486,7 +1497,7 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None):
args -- A dictionnary of arguments to parse
"""
from yunohost.domain import domain_list
from yunohost.domain import domain_list, _get_maindomain
from yunohost.user import user_info
args_dict = OrderedDict()
@ -1526,6 +1537,13 @@ def _parse_args_from_manifest(manifest, action, args={}, auth=None):
# Check for a password argument
is_password = True if arg_type == 'password' else False
if arg_type == 'domain':
arg_default = _get_maindomain()
ask_string += ' (default: {0})'.format(arg_default)
msignals.display(m18n.n('domains_available'))
for domain in domain_list(auth)['domains']:
msignals.display("- {}".format(domain))
try:
input_string = msignals.prompt(ask_string, is_password)
except NotImplementedError:

View file

@ -25,7 +25,6 @@
"""
import os
import re
import sys
import json
import errno
import time
@ -120,6 +119,8 @@ def backup_create(name=None, description=None, output_directory=None,
env_var['CAN_BIND'] = 0
else:
output_directory = archives_path
# Create archives directory if it does not exists
if not os.path.isdir(archives_path):
os.mkdir(archives_path, 0750)
@ -288,7 +289,7 @@ def backup_create(name=None, description=None, output_directory=None,
raise MoulinetteError(errno.EIO,
m18n.n('backup_archive_open_failed'))
# Add files to the arvhice
# Add files to the archive
try:
tar.add(tmp_dir, arcname='')
tar.close()
@ -298,10 +299,20 @@ def backup_create(name=None, description=None, output_directory=None,
raise MoulinetteError(errno.EIO,
m18n.n('backup_creation_failed'))
# FIXME : it looks weird that the "move info file" is not enabled if
# user activated "no_compress" ... or does it really means
# "dont_keep_track_of_this_backup_in_history" ?
# Move info file
os.rename(tmp_dir + '/info.json',
shutil.move(tmp_dir + '/info.json',
'{:s}/{:s}.info.json'.format(archives_path, name))
# If backuped to a non-default location, keep a symlink of the archive
# to that location
if output_directory != archives_path:
link = "%s/%s.tar.gz" % (archives_path, name)
os.symlink(archive_file, link)
# Clean temporary directory
if tmp_dir != output_directory:
_clean_tmp_dir()
@ -602,11 +613,23 @@ def backup_info(name, with_details=False, human_readable=False):
"""
archive_file = '%s/%s.tar.gz' % (archives_path, name)
if not os.path.isfile(archive_file):
# Check file exist (even if it's a broken symlink)
if not os.path.lexists(archive_file):
raise MoulinetteError(errno.EIO,
m18n.n('backup_archive_name_unknown', name=name))
# If symlink, retrieve the real path
if os.path.islink(archive_file):
archive_file = os.path.realpath(archive_file)
# Raise exception if link is broken (e.g. on unmounted external storage)
if not os.path.exists(archive_file):
raise MoulinetteError(errno.EIO,
m18n.n('backup_archive_broken_link', path=archive_file))
info_file = "%s/%s.info.json" % (archives_path, name)
try:
with open(info_file) as f:
# Retrieve backup info

893
src/yunohost/certificate.py Normal file
View file

@ -0,0 +1,893 @@
# -*- coding: utf-8 -*-
""" License
Copyright (C) 2016 YUNOHOST.ORG
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program; if not, see http://www.gnu.org/licenses
yunohost_certificate.py
Manage certificates, in particular Let's encrypt
"""
import os
import sys
import errno
import shutil
import pwd
import grp
import smtplib
import requests
import subprocess
import socket
import dns.resolver
import glob
from OpenSSL import crypto
from datetime import datetime
from requests.exceptions import Timeout
from yunohost.vendor.acme_tiny.acme_tiny import get_crt as sign_certificate
from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger
import yunohost.domain
from yunohost.app import app_ssowatconf
from yunohost.service import _run_service_command
logger = getActionLogger('yunohost.certmanager')
CERT_FOLDER = "/etc/yunohost/certs/"
TMP_FOLDER = "/tmp/acme-challenge-private/"
WEBROOT_FOLDER = "/tmp/acme-challenge-public/"
SELF_CA_FILE = "/etc/ssl/certs/ca-yunohost_crt.pem"
ACCOUNT_KEY_FILE = "/etc/yunohost/letsencrypt_account.pem"
SSL_DIR = '/usr/share/yunohost/yunohost-config/ssl/yunoCA'
KEY_SIZE = 3072
VALIDITY_LIMIT = 15 # days
# For tests
STAGING_CERTIFICATION_AUTHORITY = "https://acme-staging.api.letsencrypt.org"
# For prod
PRODUCTION_CERTIFICATION_AUTHORITY = "https://acme-v01.api.letsencrypt.org"
INTERMEDIATE_CERTIFICATE_URL = "https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem"
DNS_RESOLVERS = [
# FFDN DNS resolvers
# See https://www.ffdn.org/wiki/doku.php?id=formations:dns
"80.67.169.12", # FDN
"80.67.169.40", #
"89.234.141.66", # ARN
"141.255.128.100", # Aquilenet
"141.255.128.101",
"89.234.186.18", # Grifon
"80.67.188.188" # LDN
]
###############################################################################
# Front-end stuff #
###############################################################################
def certificate_status(auth, domain_list, full=False):
"""
Print the status of certificate for given domains (all by default)
Keyword argument:
domain_list -- Domains to be checked
full -- Display more info about the certificates
"""
# Check if old letsencrypt_ynh is installed
# TODO / FIXME - Remove this in the future once the letsencrypt app is
# not used anymore
_check_old_letsencrypt_app()
# If no domains given, consider all yunohost domains
if domain_list == []:
domain_list = yunohost.domain.domain_list(auth)['domains']
# Else, validate that yunohost knows the domains given
else:
yunohost_domains_list = yunohost.domain.domain_list(auth)['domains']
for domain in domain_list:
# Is it in Yunohost domain list?
if domain not in yunohost_domains_list:
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_unknown', domain=domain))
certificates = {}
for domain in domain_list:
status = _get_status(domain)
if not full:
del status["subject"]
del status["CA_name"]
del status["ACME_eligible"]
status["CA_type"] = status["CA_type"]["verbose"]
status["summary"] = status["summary"]["verbose"]
del status["domain"]
certificates[domain] = status
return {"certificates": certificates}
def certificate_install(auth, domain_list, force=False, no_checks=False, self_signed=False, staging=False):
"""
Install a Let's Encrypt certificate for given domains (all by default)
Keyword argument:
domain_list -- Domains on which to install certificates
force -- Install even if current certificate is not self-signed
no-check -- Disable some checks about the reachability of web server
before attempting the install
self-signed -- Instal self-signed certificates instead of Let's Encrypt
"""
# Check if old letsencrypt_ynh is installed
# TODO / FIXME - Remove this in the future once the letsencrypt app is
# not used anymore
_check_old_letsencrypt_app()
if self_signed:
_certificate_install_selfsigned(domain_list, force)
else:
_certificate_install_letsencrypt(
auth, domain_list, force, no_checks, staging)
def _certificate_install_selfsigned(domain_list, force=False):
for domain in domain_list:
# Paths of files and folder we'll need
date_tag = datetime.now().strftime("%Y%m%d.%H%M%S")
new_cert_folder = "%s/%s-history/%s-selfsigned" % (
CERT_FOLDER, domain, date_tag)
conf_template = os.path.join(SSL_DIR, "openssl.cnf")
csr_file = os.path.join(SSL_DIR, "certs", "yunohost_csr.pem")
conf_file = os.path.join(new_cert_folder, "openssl.cnf")
key_file = os.path.join(new_cert_folder, "key.pem")
crt_file = os.path.join(new_cert_folder, "crt.pem")
ca_file = os.path.join(new_cert_folder, "ca.pem")
# Check we ain't trying to overwrite a good cert !
current_cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem")
if not force and os.path.isfile(current_cert_file):
status = _get_status(domain)
if status["summary"]["code"] in ('good', 'great'):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_attempt_to_replace_valid_cert', domain=domain))
# Create output folder for new certificate stuff
os.makedirs(new_cert_folder)
# Create our conf file, based on template, replacing the occurences of
# "yunohost.org" with the given domain
with open(conf_file, "w") as f, open(conf_template, "r") as template:
for line in template:
f.write(line.replace("yunohost.org", domain))
# Use OpenSSL command line to create a certificate signing request,
# and self-sign the cert
commands = [
"openssl req -new -config %s -days 3650 -out %s -keyout %s -nodes -batch"
% (conf_file, csr_file, key_file),
"openssl ca -config %s -days 3650 -in %s -out %s -batch"
% (conf_file, csr_file, crt_file),
]
for command in commands:
p = subprocess.Popen(
command.split(), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
out, _ = p.communicate()
if p.returncode != 0:
logger.warning(out)
raise MoulinetteError(
errno.EIO, m18n.n('domain_cert_gen_failed'))
else:
logger.info(out)
# Link the CA cert (not sure it's actually needed in practice though,
# since we append it at the end of crt.pem. For instance for Let's
# Encrypt certs, we only need the crt.pem and key.pem)
os.symlink(SELF_CA_FILE, ca_file)
# Append ca.pem at the end of crt.pem
with open(ca_file, "r") as ca_pem, open(crt_file, "a") as crt_pem:
crt_pem.write("\n")
crt_pem.write(ca_pem.read())
# Set appropriate permissions
_set_permissions(new_cert_folder, "root", "root", 0755)
_set_permissions(key_file, "root", "metronome", 0640)
_set_permissions(crt_file, "root", "metronome", 0640)
_set_permissions(conf_file, "root", "root", 0600)
# Actually enable the certificate we created
_enable_certificate(domain, new_cert_folder)
# Check new status indicate a recently created self-signed certificate
status = _get_status(domain)
if status and status["CA_type"]["code"] == "self-signed" and status["validity"] > 3648:
logger.success(
m18n.n("certmanager_cert_install_success_selfsigned", domain=domain))
else:
logger.error(
"Installation of self-signed certificate installation for %s failed !", domain)
def _certificate_install_letsencrypt(auth, domain_list, force=False, no_checks=False, staging=False):
if not os.path.exists(ACCOUNT_KEY_FILE):
_generate_account_key()
# If no domains given, consider all yunohost domains with self-signed
# certificates
if domain_list == []:
for domain in yunohost.domain.domain_list(auth)['domains']:
status = _get_status(domain)
if status["CA_type"]["code"] != "self-signed":
continue
domain_list.append(domain)
# Else, validate that yunohost knows the domains given
else:
for domain in domain_list:
yunohost_domains_list = yunohost.domain.domain_list(auth)['domains']
if domain not in yunohost_domains_list:
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_unknown', domain=domain))
# Is it self-signed?
status = _get_status(domain)
if not force and status["CA_type"]["code"] != "self-signed":
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_cert_not_selfsigned', domain=domain))
if staging:
logger.warning(
"Please note that you used the --staging option, and that no new certificate will actually be enabled !")
# Actual install steps
for domain in domain_list:
logger.info(
"Now attempting install of certificate for domain %s!", domain)
try:
if not no_checks:
_check_domain_is_ready_for_ACME(domain)
_configure_for_acme_challenge(auth, domain)
_fetch_and_enable_new_certificate(domain, staging)
_install_cron()
logger.success(
m18n.n("certmanager_cert_install_success", domain=domain))
except Exception as e:
logger.error("Certificate installation for %s failed !\nException: %s", domain, e)
def certificate_renew(auth, domain_list, force=False, no_checks=False, email=False, staging=False):
"""
Renew Let's Encrypt certificate for given domains (all by default)
Keyword argument:
domain_list -- Domains for which to renew the certificates
force -- Ignore the validity threshold (15 days)
no-check -- Disable some checks about the reachability of web server
before attempting the renewing
email -- Emails root if some renewing failed
"""
# Check if old letsencrypt_ynh is installed
# TODO / FIXME - Remove this in the future once the letsencrypt app is
# not used anymore
_check_old_letsencrypt_app()
# If no domains given, consider all yunohost domains with Let's Encrypt
# certificates
if domain_list == []:
for domain in yunohost.domain.domain_list(auth)['domains']:
# Does it have a Let's Encrypt cert?
status = _get_status(domain)
if status["CA_type"]["code"] != "lets-encrypt":
continue
# Does it expire soon?
if status["validity"] > VALIDITY_LIMIT and not force:
continue
# Check ACME challenge configured for given domain
if not _check_acme_challenge_configuration(domain):
logger.warning(m18n.n(
'certmanager_acme_not_configured_for_domain', domain=domain))
continue
domain_list.append(domain)
if len(domain_list) == 0:
logger.info("No certificate needs to be renewed.")
# Else, validate the domain list given
else:
for domain in domain_list:
# Is it in Yunohost dmomain list?
if domain not in yunohost.domain.domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_unknown', domain=domain))
status = _get_status(domain)
# Does it expire soon?
if status["validity"] > VALIDITY_LIMIT and not force:
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_attempt_to_renew_valid_cert', domain=domain))
# Does it have a Let's Encrypt cert?
if status["CA_type"]["code"] != "lets-encrypt":
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_attempt_to_renew_nonLE_cert', domain=domain))
# Check ACME challenge configured for given domain
if not _check_acme_challenge_configuration(domain):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_acme_not_configured_for_domain', domain=domain))
if staging:
logger.warning(
"Please note that you used the --staging option, and that no new certificate will actually be enabled !")
# Actual renew steps
for domain in domain_list:
logger.info(
"Now attempting renewing of certificate for domain %s !", domain)
try:
if not no_checks:
_check_domain_is_ready_for_ACME(domain)
_fetch_and_enable_new_certificate(domain, staging)
logger.success(
m18n.n("certmanager_cert_renew_success", domain=domain))
except Exception as e:
import traceback
from StringIO import StringIO
stack = StringIO()
traceback.print_exc(file=stack)
logger.error("Certificate renewing for %s failed !", domain)
logger.error(stack.getvalue())
logger.error(str(e))
if email:
logger.error("Sending email with details to root ...")
_email_renewing_failed(domain, e, stack.getvalue())
###############################################################################
# Back-end stuff #
###############################################################################
def _check_old_letsencrypt_app():
installedAppIds = [app["id"] for app in yunohost.app.app_list(installed=True)["apps"]]
if "letsencrypt" not in installedAppIds:
return
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_old_letsencrypt_app_detected'))
def _install_cron():
cron_job_file = "/etc/cron.daily/yunohost-certificate-renew"
with open(cron_job_file, "w") as f:
f.write("#!/bin/bash\n")
f.write("yunohost domain cert-renew --email\n")
_set_permissions(cron_job_file, "root", "root", 0755)
def _email_renewing_failed(domain, exception_message, stack):
from_ = "certmanager@%s (Certificate Manager)" % domain
to_ = "root"
subject_ = "Certificate renewing attempt for %s failed!" % domain
logs = _tail(50, "/var/log/yunohost/yunohost-cli.log")
text = """
An attempt for renewing the certificate for domain %s failed with the following
error :
%s
%s
Here's the tail of /var/log/yunohost/yunohost-cli.log, which might help to
investigate :
%s
-- Certificate Manager
""" % (domain, exception_message, stack, logs)
message = """
From: %s
To: %s
Subject: %s
%s
""" % (from_, to_, subject_, text)
smtp = smtplib.SMTP("localhost")
smtp.sendmail(from_, [to_], message)
smtp.quit()
def _configure_for_acme_challenge(auth, domain):
nginx_conf_folder = "/etc/nginx/conf.d/%s.d" % domain
nginx_conf_file = "%s/000-acmechallenge.conf" % nginx_conf_folder
nginx_configuration = '''
location '/.well-known/acme-challenge'
{
default_type "text/plain";
alias %s;
}
''' % WEBROOT_FOLDER
# Check there isn't a conflicting file for the acme-challenge well-known
# uri
for path in glob.glob('%s/*.conf' % nginx_conf_folder):
if path == nginx_conf_file:
continue
with open(path) as f:
contents = f.read()
if '/.well-known/acme-challenge' in contents:
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_conflicting_nginx_file', filepath=path))
# Write the conf
if os.path.exists(nginx_conf_file):
logger.info(
"Nginx configuration file for ACME challenge already exists for domain, skipping.")
return
logger.info(
"Adding Nginx configuration file for Acme challenge for domain %s.", domain)
with open(nginx_conf_file, "w") as f:
f.write(nginx_configuration)
# Assume nginx conf is okay, and reload it
# (FIXME : maybe add a check that it is, using nginx -t, haven't found
# any clean function already implemented in yunohost to do this though)
_run_service_command("reload", "nginx")
app_ssowatconf(auth)
def _check_acme_challenge_configuration(domain):
# Check nginx conf file exists
nginx_conf_folder = "/etc/nginx/conf.d/%s.d" % domain
nginx_conf_file = "%s/000-acmechallenge.conf" % nginx_conf_folder
if not os.path.exists(nginx_conf_file):
return False
else:
return True
def _fetch_and_enable_new_certificate(domain, staging=False):
# Make sure tmp folder exists
logger.debug("Making sure tmp folders exists...")
if not os.path.exists(WEBROOT_FOLDER):
os.makedirs(WEBROOT_FOLDER)
if not os.path.exists(TMP_FOLDER):
os.makedirs(TMP_FOLDER)
_set_permissions(WEBROOT_FOLDER, "root", "www-data", 0650)
_set_permissions(TMP_FOLDER, "root", "root", 0640)
# Prepare certificate signing request
logger.info(
"Prepare key and certificate signing request (CSR) for %s...", domain)
domain_key_file = "%s/%s.pem" % (TMP_FOLDER, domain)
_generate_key(domain_key_file)
_set_permissions(domain_key_file, "root", "metronome", 0640)
_prepare_certificate_signing_request(domain, domain_key_file, TMP_FOLDER)
# Sign the certificate
logger.info("Now using ACME Tiny to sign the certificate...")
domain_csr_file = "%s/%s.csr" % (TMP_FOLDER, domain)
if staging:
certification_authority = STAGING_CERTIFICATION_AUTHORITY
else:
certification_authority = PRODUCTION_CERTIFICATION_AUTHORITY
try:
signed_certificate = sign_certificate(ACCOUNT_KEY_FILE,
domain_csr_file,
WEBROOT_FOLDER,
log=logger,
CA=certification_authority)
except ValueError as e:
if "urn:acme:error:rateLimited" in str(e):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_hit_rate_limit', domain=domain))
else:
logger.error(str(e))
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_cert_signing_failed'))
except Exception as e:
logger.error(str(e))
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_cert_signing_failed'))
try:
intermediate_certificate = requests.get(INTERMEDIATE_CERTIFICATE_URL, timeout=30).text
except Timeout as e:
raise MoulinetteError(errno.EINVAL, m18n.n('certmanager_couldnt_fetch_intermediate_cert'))
# Now save the key and signed certificate
logger.info("Saving the key and signed certificate...")
# Create corresponding directory
date_tag = datetime.now().strftime("%Y%m%d.%H%M%S")
if staging:
folder_flag = "staging"
else:
folder_flag = "letsencrypt"
new_cert_folder = "%s/%s-history/%s-%s" % (
CERT_FOLDER, domain, date_tag, folder_flag)
os.makedirs(new_cert_folder)
_set_permissions(new_cert_folder, "root", "root", 0655)
# Move the private key
domain_key_file_finaldest = os.path.join(new_cert_folder, "key.pem")
shutil.move(domain_key_file, domain_key_file_finaldest)
_set_permissions(domain_key_file_finaldest, "root", "metronome", 0640)
# Write the cert
domain_cert_file = os.path.join(new_cert_folder, "crt.pem")
with open(domain_cert_file, "w") as f:
f.write(signed_certificate)
f.write(intermediate_certificate)
_set_permissions(domain_cert_file, "root", "metronome", 0640)
if staging:
return
_enable_certificate(domain, new_cert_folder)
# Check the status of the certificate is now good
status_summary = _get_status(domain)["summary"]
if status_summary["code"] != "great":
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_certificate_fetching_or_enabling_failed', domain=domain))
def _prepare_certificate_signing_request(domain, key_file, output_folder):
# Init a request
csr = crypto.X509Req()
# Set the domain
csr.get_subject().CN = domain
# Set the key
with open(key_file, 'rt') as f:
key = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
csr.set_pubkey(key)
# Sign the request
csr.sign(key, "sha256")
# Save the request in tmp folder
csr_file = output_folder + domain + ".csr"
logger.info("Saving to %s.", csr_file)
with open(csr_file, "w") as f:
f.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, csr))
def _get_status(domain):
cert_file = os.path.join(CERT_FOLDER, domain, "crt.pem")
if not os.path.isfile(cert_file):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_no_cert_file', domain=domain, file=cert_file))
try:
cert = crypto.load_certificate(
crypto.FILETYPE_PEM, open(cert_file).read())
except Exception as exception:
import traceback
traceback.print_exc(file=sys.stdout)
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_cannot_read_cert', domain=domain, file=cert_file, reason=exception))
cert_subject = cert.get_subject().CN
cert_issuer = cert.get_issuer().CN
valid_up_to = datetime.strptime(cert.get_notAfter(), "%Y%m%d%H%M%SZ")
days_remaining = (valid_up_to - datetime.now()).days
if cert_issuer == _name_self_CA():
CA_type = {
"code": "self-signed",
"verbose": "Self-signed",
}
elif cert_issuer.startswith("Let's Encrypt"):
CA_type = {
"code": "lets-encrypt",
"verbose": "Let's Encrypt",
}
elif cert_issuer.startswith("Fake LE"):
CA_type = {
"code": "fake-lets-encrypt",
"verbose": "Fake Let's Encrypt",
}
else:
CA_type = {
"code": "other-unknown",
"verbose": "Other / Unknown",
}
if days_remaining <= 0:
status_summary = {
"code": "critical",
"verbose": "CRITICAL",
}
elif CA_type["code"] in ("self-signed", "fake-lets-encrypt"):
status_summary = {
"code": "warning",
"verbose": "WARNING",
}
elif days_remaining < VALIDITY_LIMIT:
status_summary = {
"code": "attention",
"verbose": "About to expire",
}
elif CA_type["code"] == "other-unknown":
status_summary = {
"code": "good",
"verbose": "Good",
}
elif CA_type["code"] == "lets-encrypt":
status_summary = {
"code": "great",
"verbose": "Great!",
}
else:
status_summary = {
"code": "unknown",
"verbose": "Unknown?",
}
try:
_check_domain_is_ready_for_ACME(domain)
ACME_eligible = True
except:
ACME_eligible = False
return {
"domain": domain,
"subject": cert_subject,
"CA_name": cert_issuer,
"CA_type": CA_type,
"validity": days_remaining,
"summary": status_summary,
"ACME_eligible": ACME_eligible
}
###############################################################################
# Misc small stuff ... #
###############################################################################
def _generate_account_key():
logger.info("Generating account key ...")
_generate_key(ACCOUNT_KEY_FILE)
_set_permissions(ACCOUNT_KEY_FILE, "root", "root", 0400)
def _generate_key(destination_path):
k = crypto.PKey()
k.generate_key(crypto.TYPE_RSA, KEY_SIZE)
with open(destination_path, "w") as f:
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, k))
def _set_permissions(path, user, group, permissions):
uid = pwd.getpwnam(user).pw_uid
gid = grp.getgrnam(group).gr_gid
os.chown(path, uid, gid)
os.chmod(path, permissions)
def _enable_certificate(domain, new_cert_folder):
logger.info("Enabling the certificate for domain %s ...", domain)
live_link = os.path.join(CERT_FOLDER, domain)
# If a live link (or folder) already exists
if os.path.exists(live_link):
# If it's not a link ... expect if to be a folder
if not os.path.islink(live_link):
# Backup it and remove it
_backup_current_cert(domain)
shutil.rmtree(live_link)
# Else if it's a link, simply delete it
elif os.path.lexists(live_link):
os.remove(live_link)
os.symlink(new_cert_folder, live_link)
logger.info("Restarting services...")
for service in ("postfix", "dovecot", "metronome"):
_run_service_command("restart", service)
_run_service_command("reload", "nginx")
def _backup_current_cert(domain):
logger.info("Backuping existing certificate for domain %s", domain)
cert_folder_domain = os.path.join(CERT_FOLDER, domain)
date_tag = datetime.now().strftime("%Y%m%d.%H%M%S")
backup_folder = "%s-backups/%s" % (cert_folder_domain, date_tag)
shutil.copytree(cert_folder_domain, backup_folder)
def _check_domain_is_ready_for_ACME(domain):
public_ip = yunohost.domain.get_public_ip()
# Check if IP from DNS matches public IP
if not _dns_ip_match_public_ip(public_ip, domain):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_dns_ip_differs_from_public_ip', domain=domain))
# Check if domain seems to be accessible through HTTP?
if not _domain_is_accessible_through_HTTP(public_ip, domain):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_http_not_working', domain=domain))
# Check if domain is resolved locally (Might happen despite the previous
# checks because of dns propagation ?... Acme-tiny won't work in that case,
# because it explicitly requests() the domain.)
if not _domain_is_resolved_locally(public_ip, domain):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_domain_not_resolved_locally', domain=domain))
def _dns_ip_match_public_ip(public_ip, domain):
try:
resolver = dns.resolver.Resolver()
resolver.nameservers = DNS_RESOLVERS
answers = resolver.query(domain, "A")
except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN):
raise MoulinetteError(errno.EINVAL, m18n.n(
'certmanager_error_no_A_record', domain=domain))
dns_ip = str(answers[0])
return dns_ip == public_ip
def _domain_is_accessible_through_HTTP(ip, domain):
try:
requests.head("http://" + ip, headers={"Host": domain}, timeout=10)
except Timeout as e:
logger.warning(m18n.n('certmanager_http_check_timeout', domain=domain, ip=ip))
return False
except Exception as e:
logger.debug("Couldn't reach domain '%s' by requesting this ip '%s' because: %s" % (domain, ip, e))
return False
return True
def _domain_is_resolved_locally(public_ip, domain):
try:
ip = socket.gethostbyname(domain)
except socket.error as e:
logger.debug("Couldn't get domain '%s' ip because: %s" % (domain, e))
return False
logger.debug("Domain '%s' ip is %s, except it to be 127.0.0.1 or %s" % (domain, ip, public_ip))
return ip in ["127.0.0.1", public_ip]
def _name_self_CA():
ca_conf = os.path.join(SSL_DIR, "openssl.ca.cnf")
if not os.path.exists(ca_conf):
logger.warning(m18n.n('certmanager_self_ca_conf_file_not_found', file=ca_conf))
return ""
with open(ca_conf) as f:
lines = f.readlines()
for line in lines:
if line.startswith("commonName_default"):
return line.split()[2]
logger.warning(m18n.n('certmanager_unable_to_parse_self_CA_name', file=ca_conf))
return ""
def _tail(n, file_path):
stdin, stdout = os.popen2("tail -n %s '%s'" % (n, file_path))
stdin.close()
lines = stdout.readlines()
stdout.close()
return "".join(lines)

View file

@ -24,19 +24,20 @@
Manage domains
"""
import os
import sys
import datetime
import re
import shutil
import json
import yaml
import errno
import requests
from urllib import urlopen
from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger
import yunohost.certificate
from yunohost.service import service_regen_conf
logger = getActionLogger('yunohost.domain')
@ -113,44 +114,13 @@ def domain_add(auth, domain, dyndns=False):
m18n.n('domain_dyndns_root_unknown'))
try:
# Commands
ssl_dir = '/usr/share/yunohost/yunohost-config/ssl/yunoCA'
ssl_domain_path = '/etc/yunohost/certs/%s' % domain
with open('%s/serial' % ssl_dir, 'r') as f:
serial = f.readline().rstrip()
try: os.listdir(ssl_domain_path)
except OSError: os.makedirs(ssl_domain_path)
command_list = [
'cp %s/openssl.cnf %s' % (ssl_dir, ssl_domain_path),
'sed -i "s/yunohost.org/%s/g" %s/openssl.cnf' % (domain, ssl_domain_path),
'openssl req -new -config %s/openssl.cnf -days 3650 -out %s/certs/yunohost_csr.pem -keyout %s/certs/yunohost_key.pem -nodes -batch'
% (ssl_domain_path, ssl_dir, ssl_dir),
'openssl ca -config %s/openssl.cnf -days 3650 -in %s/certs/yunohost_csr.pem -out %s/certs/yunohost_crt.pem -batch'
% (ssl_domain_path, ssl_dir, ssl_dir),
'ln -s /etc/ssl/certs/ca-yunohost_crt.pem %s/ca.pem' % ssl_domain_path,
'cp %s/certs/yunohost_key.pem %s/key.pem' % (ssl_dir, ssl_domain_path),
'cp %s/newcerts/%s.pem %s/crt.pem' % (ssl_dir, serial, ssl_domain_path),
'chmod 755 %s' % ssl_domain_path,
'chmod 640 %s/key.pem' % ssl_domain_path,
'chmod 640 %s/crt.pem' % ssl_domain_path,
'chmod 600 %s/openssl.cnf' % ssl_domain_path,
'chown root:metronome %s/key.pem' % ssl_domain_path,
'chown root:metronome %s/crt.pem' % ssl_domain_path,
'cat %s/ca.pem >> %s/crt.pem' % (ssl_domain_path, ssl_domain_path)
]
for command in command_list:
if os.system(command) != 0:
raise MoulinetteError(errno.EIO,
m18n.n('domain_cert_gen_failed'))
yunohost.certificate._certificate_install_selfsigned([domain], False)
try:
auth.validate_uniqueness({'virtualdomain': domain})
except MoulinetteError:
raise MoulinetteError(errno.EEXIST, m18n.n('domain_exists'))
attr_dict['virtualdomain'] = domain
if not auth.add('virtualdomain=%s,ou=domains' % domain, attr_dict):
@ -161,11 +131,14 @@ def domain_add(auth, domain, dyndns=False):
service_regen_conf(names=[
'nginx', 'metronome', 'dnsmasq', 'rmilter'])
os.system('yunohost app ssowatconf > /dev/null 2>&1')
except IOError: pass
except IOError:
pass
except:
# Force domain removal silently
try: domain_remove(auth, domain, True)
except: pass
try:
domain_remove(auth, domain, True)
except:
pass
raise
hook_callback('post_domain_add', args=[domain])
@ -187,6 +160,10 @@ def domain_remove(auth, domain, force=False):
if not force and domain not in domain_list(auth)['domains']:
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
# Check domain is not the main domain
if domain == _get_maindomain():
raise MoulinetteError(errno.EINVAL, m18n.n('domain_cannot_remove_main'))
# Check if apps are installed on the domain
for app in os.listdir('/etc/yunohost/apps/'):
with open('/etc/yunohost/apps/' + app + '/settings.yml') as f:
@ -286,6 +263,18 @@ def domain_dns_conf(domain, ttl=None):
return result
def domain_cert_status(auth, domain_list, full=False):
return yunohost.certificate.certificate_status(auth, domain_list, full)
def domain_cert_install(auth, domain_list, force=False, no_checks=False, self_signed=False, staging=False):
return yunohost.certificate.certificate_install(auth, domain_list, force, no_checks, self_signed, staging)
def domain_cert_renew(auth, domain_list, force=False, no_checks=False, email=False, staging=False):
return yunohost.certificate.certificate_renew(auth, domain_list, force, no_checks, email, staging)
def get_public_ip(protocol=4):
"""Retrieve the public IP address from ip.yunohost.org"""
if protocol == 4:
@ -301,3 +290,14 @@ def get_public_ip(protocol=4):
logger.debug('cannot retrieve public IPv%d' % protocol, exc_info=1)
raise MoulinetteError(errno.ENETUNREACH,
m18n.n('no_internet_connection'))
def _get_maindomain():
with open('/etc/yunohost/current_host', 'r') as f:
maindomain = f.readline().rstrip()
return maindomain
def _set_maindomain(domain):
with open('/etc/yunohost/current_host', 'w') as f:
f.write(domain)

View file

@ -94,8 +94,8 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None
logger.info(m18n.n('dyndns_key_generating'))
os.system('cd /etc/yunohost/dyndns && ' \
'dnssec-keygen -a hmac-md5 -b 128 -n USER %s' % domain)
os.system('cd /etc/yunohost/dyndns && '
'dnssec-keygen -a hmac-md5 -b 128 -r /dev/urandom -n USER %s' % domain)
os.system('chmod 600 /etc/yunohost/dyndns/*.key /etc/yunohost/dyndns/*.private')
key_file = glob.glob('/etc/yunohost/dyndns/*.key')[0]
@ -108,8 +108,10 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None
except requests.ConnectionError:
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
if r.status_code != 201:
try: error = json.loads(r.text)['error']
except: error = "Server error"
try:
error = json.loads(r.text)['error']
except:
error = "Server error"
raise MoulinetteError(errno.EPERM,
m18n.n('dyndns_registration_failed', error=error))

View file

@ -224,7 +224,7 @@ def firewall_reload(skip_upnp=False):
# Iterate over ports and add rule
for protocol in ['TCP', 'UDP']:
for port in firewall['ipv4'][protocol]:
rules.append("iptables -w -A INPUT -p %s --dport %s -j ACCEPT" \
rules.append("iptables -w -A INPUT -p %s --dport %s -j ACCEPT"
% (protocol, process.quote(str(port))))
rules += [
"iptables -w -A INPUT -i lo -j ACCEPT",
@ -253,7 +253,7 @@ def firewall_reload(skip_upnp=False):
# Iterate over ports and add rule
for protocol in ['TCP', 'UDP']:
for port in firewall['ipv6'][protocol]:
rules.append("ip6tables -w -A INPUT -p %s --dport %s -j ACCEPT" \
rules.append("ip6tables -w -A INPUT -p %s --dport %s -j ACCEPT"
% (protocol, process.quote(str(port))))
rules += [
"ip6tables -w -A INPUT -i lo -j ACCEPT",
@ -308,7 +308,8 @@ def firewall_upnp(action='status', no_refresh=False):
try:
# Remove old cron job
os.remove('/etc/cron.d/yunohost-firewall')
except: pass
except:
pass
action = 'status'
no_refresh = False
@ -330,7 +331,8 @@ def firewall_upnp(action='status', no_refresh=False):
try:
# Remove cron job
os.remove(upnp_cron_job)
except: pass
except:
pass
enabled = False
if action == 'status':
no_refresh = True
@ -364,7 +366,8 @@ def firewall_upnp(action='status', no_refresh=False):
if upnpc.getspecificportmapping(port, protocol):
try:
upnpc.deleteportmapping(port, protocol)
except: pass
except:
pass
if not enabled:
continue
try:
@ -444,12 +447,14 @@ def _get_ssh_port(default=22):
pass
return default
def _update_firewall_file(rules):
"""Make a backup and write new rules to firewall file"""
os.system("cp {0} {0}.old".format(firewall_file))
with open(firewall_file, 'w') as f:
yaml.safe_dump(rules, f, default_flow_style=False)
def _on_rule_command_error(returncode, cmd, output):
"""Callback for rules commands error"""
# Log error and continue commands execution

View file

@ -24,11 +24,8 @@
Manage hooks
"""
import os
import sys
import re
import json
import errno
import subprocess
from glob import iglob
from moulinette.core import MoulinetteError
@ -315,7 +312,7 @@ def hook_exec(path, args=None, raise_on_error=False, no_trace=False,
if path[0] != '/':
path = os.path.realpath(path)
if not os.path.isfile(path):
raise MoulinetteError(errno.EIO, m18n.g('file_not_exist'))
raise MoulinetteError(errno.EIO, m18n.g('file_not_exist', path=path))
# Construct command variables
cmd_args = ''

View file

@ -35,7 +35,7 @@ import errno
import os
import dns.resolver
import cPickle as pickle
from datetime import datetime, timedelta
from datetime import datetime
from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger
@ -87,7 +87,7 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False):
# Retrieve monitoring for unit(s)
for u in units:
if u == 'io':
## Define setter
# Define setter
if len(units) > 1:
def _set(dn, dvalue):
try:
@ -111,7 +111,7 @@ def monitor_disk(units=None, mountpoint=None, human_readable=False):
for dname in devices_names:
_set(dname, 'not-available')
elif u == 'filesystem':
## Define setter
# Define setter
if len(units) > 1:
def _set(dn, dvalue):
try:

View file

@ -36,7 +36,7 @@ from difflib import unified_diff
from moulinette.core import MoulinetteError
from moulinette.utils import log, filesystem
from yunohost.hook import hook_list, hook_callback
from yunohost.hook import hook_callback
base_conf_path = '/home/yunohost.conf'
@ -315,6 +315,7 @@ def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False,
# Execute hooks for pre-regen
pre_args = ['pre', ] + common_args
def _pre_call(name, priority, path, args):
# create the pending conf directory for the service
service_pending_path = os.path.join(pending_conf_dir, name)
@ -336,7 +337,7 @@ def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False,
# Iterate over services and process pending conf
for service, conf_files in _get_pending_conf(names).items():
logger.info(m18n.n(
'service_regenconf_pending_applying' if not dry_run else \
'service_regenconf_pending_applying' if not dry_run else
'service_regenconf_dry_pending_applying',
service=service))
@ -444,7 +445,7 @@ def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False,
continue
elif not failed_regen:
logger.success(m18n.n(
'service_conf_updated' if not dry_run else \
'service_conf_updated' if not dry_run else
'service_conf_would_be_updated',
service=service))
if succeed_regen and not dry_run:
@ -462,6 +463,7 @@ def service_regen_conf(names=[], with_diff=False, force=False, dry_run=False,
# Execute hooks for post-regen
post_args = ['post', ] + common_args
def _pre_call(name, priority, path, args):
# append coma-separated applied changes for the service
if name in result and result[name]['applied']:
@ -556,7 +558,8 @@ def _tail(file, n, offset=None):
return lines[-to_read:offset and -offset or None]
avg_line_length *= 1.3
except IOError: return []
except IOError:
return []
def _get_files_diff(orig_file, new_file, as_string=False, skip_header=True):

View file

@ -24,14 +24,13 @@
Specific tools
"""
import os
import sys
import yaml
import re
import getpass
import requests
import json
import errno
import logging
import subprocess
import pwd
from collections import OrderedDict
import apt
@ -40,11 +39,11 @@ import apt.progress
from moulinette.core import MoulinetteError, init_authenticator
from moulinette.utils.log import getActionLogger
from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list
from yunohost.domain import domain_add, domain_list, get_public_ip
from yunohost.domain import domain_add, domain_list, get_public_ip, _get_maindomain, _set_maindomain
from yunohost.dyndns import dyndns_subscribe
from yunohost.firewall import firewall_upnp, firewall_reload
from yunohost.firewall import firewall_upnp
from yunohost.service import service_status, service_regen_conf, service_log
from yunohost.monitor import monitor_disk, monitor_network, monitor_system
from yunohost.monitor import monitor_disk, monitor_system
from yunohost.utils.packages import ynh_packages_version
apps_setting_path= '/etc/yunohost/apps/'
@ -52,22 +51,34 @@ apps_setting_path= '/etc/yunohost/apps/'
logger = getActionLogger('yunohost.tools')
def tools_ldapinit(auth):
def tools_ldapinit():
"""
YunoHost LDAP initialization
"""
# Instantiate LDAP Authenticator
auth = init_authenticator(('ldap', 'default'),
{'uri': "ldap://localhost:389",
'base_dn': "dc=yunohost,dc=org",
'user_rdn': "cn=admin" })
auth.authenticate('yunohost')
with open('/usr/share/yunohost/yunohost-config/moulinette/ldap_scheme.yml') as f:
ldap_map = yaml.load(f)
for rdn, attr_dict in ldap_map['parents'].items():
try: auth.add(rdn, attr_dict)
except: pass
try:
auth.add(rdn, attr_dict)
except:
pass
for rdn, attr_dict in ldap_map['children'].items():
try: auth.add(rdn, attr_dict)
except: pass
try:
auth.add(rdn, attr_dict)
except:
pass
admin_dict = {
'cn': 'admin',
@ -83,8 +94,18 @@ def tools_ldapinit(auth):
auth.update('cn=admin', admin_dict)
logger.success(m18n.n('ldap_initialized'))
# Force nscd to refresh cache to take admin creation into account
subprocess.call(['nscd', '-i', 'passwd'])
# Check admin actually exists now
try:
pwd.getpwnam("admin")
except KeyError:
logger.error(m18n.n('ldap_init_failed_to_create_admin'))
raise MoulinetteError(errno.EINVAL, m18n.n('installation_failed'))
logger.success(m18n.n('ldap_initialized'))
return auth
def tools_adminpw(auth, new_password):
"""
@ -104,56 +125,49 @@ def tools_adminpw(auth, new_password):
logger.success(m18n.n('admin_password_changed'))
def tools_maindomain(auth, old_domain=None, new_domain=None, dyndns=False):
def tools_maindomain(auth, new_domain=None):
"""
Main domain change tool
Check the current main domain, or change it
Keyword argument:
new_domain
old_domain
new_domain -- The new domain to be set as the main domain
"""
if not old_domain:
with open('/etc/yunohost/current_host', 'r') as f:
old_domain = f.readline().rstrip()
# If no new domain specified, we return the current main domain
if not new_domain:
return { 'current_main_domain': old_domain }
return {'current_main_domain': _get_maindomain()}
if not new_domain:
raise MoulinetteError(errno.EINVAL, m18n.n('new_domain_required'))
# Check domain exists
if new_domain not in domain_list(auth)['domains']:
domain_add(auth, new_domain)
raise MoulinetteError(errno.EINVAL, m18n.n('domain_unknown'))
os.system('rm /etc/ssl/private/yunohost_key.pem')
os.system('rm /etc/ssl/certs/yunohost_crt.pem')
# 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
command_list = [
'ln -s /etc/yunohost/certs/%s/key.pem /etc/ssl/private/yunohost_key.pem' % new_domain,
'ln -s /etc/yunohost/certs/%s/crt.pem /etc/ssl/certs/yunohost_crt.pem' % new_domain,
'echo %s > /etc/yunohost/current_host' % new_domain,
]
for command in command_list:
if os.system(command) != 0:
raise MoulinetteError(errno.EPERM,
m18n.n('maindomain_change_failed'))
if dyndns and len(new_domain.split('.')) >= 3:
try:
r = requests.get('https://dyndns.yunohost.org/domains')
except requests.ConnectionError:
pass
else:
dyndomains = json.loads(r.text)
dyndomain = '.'.join(new_domain.split('.')[1:])
if dyndomain in dyndomains:
dyndns_subscribe(domain=new_domain)
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 MoulinetteError(errno.EPERM, m18n.n('maindomain_change_failed'))
# Regen configurations
try:
with open('/etc/yunohost/installed', 'r') as f:
service_regen_conf()
except IOError: pass
except IOError:
pass
logger.success(m18n.n('maindomain_changed'))
@ -164,7 +178,8 @@ def tools_postinstall(domain, password, ignore_dyndns=False):
Keyword argument:
domain -- YunoHost main domain
ignore_dyndns -- Do not subscribe domain to a DynDNS service
ignore_dyndns -- Do not subscribe domain to a DynDNS service (only
needed for nohost.me, noho.st domains)
password -- YunoHost admin password
"""
@ -182,25 +197,23 @@ def tools_postinstall(domain, password, ignore_dyndns=False):
else:
dyndomains = json.loads(r.text)
dyndomain = '.'.join(domain.split('.')[1:])
if dyndomain in dyndomains:
if requests.get('https://dyndns.yunohost.org/test/%s' % domain).status_code == 200:
dyndns = True
else:
raise MoulinetteError(errno.EEXIST,
m18n.n('dyndns_unavailable'))
else:
dyndns = False
else:
dyndns = False
logger.info(m18n.n('yunohost_installing'))
# Instantiate LDAP Authenticator
auth = init_authenticator(('ldap', 'default'),
{'uri': "ldap://localhost:389",
'base_dn': "dc=yunohost,dc=org",
'user_rdn': "cn=admin" })
auth.authenticate('yunohost')
# Initialize LDAP for YunoHost
# TODO: Improve this part by integrate ldapinit into conf_regen hook
tools_ldapinit(auth)
auth = tools_ldapinit()
# Create required folders
folders_to_create = [
@ -212,8 +225,10 @@ def tools_postinstall(domain, password, ignore_dyndns=False):
]
for folder in folders_to_create:
try: os.listdir(folder)
except OSError: os.makedirs(folder)
try:
os.listdir(folder)
except OSError:
os.makedirs(folder)
# Change folders permissions
os.system('chmod 755 /home/yunohost.app')
@ -226,6 +241,9 @@ def tools_postinstall(domain, password, ignore_dyndns=False):
try:
with open('/etc/ssowat/conf.json.persistent') as json_conf:
ssowat_conf = json.loads(str(json_conf.read()))
except ValueError as e:
raise MoulinetteError(errno.EINVAL,
m18n.n('ssowat_persistent_conf_read_error', error=e.strerror))
except IOError:
ssowat_conf = {}
@ -234,8 +252,13 @@ def tools_postinstall(domain, password, ignore_dyndns=False):
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 MoulinetteError(errno.EPERM,
m18n.n('ssowat_persistent_conf_write_error', error=e.strerror))
os.system('chmod 644 /etc/ssowat/conf.json.persistent')
@ -259,7 +282,8 @@ def tools_postinstall(domain, password, ignore_dyndns=False):
m18n.n('yunohost_ca_creation_failed'))
# New domain config
tools_maindomain(auth, old_domain='yunohost.org', new_domain=domain, dyndns=dyndns)
domain_add(auth, domain, dyndns)
tools_maindomain(auth, domain)
# Generate SSOwat configuration file
app_ssowatconf(auth)
@ -277,7 +301,6 @@ def tools_postinstall(domain, password, ignore_dyndns=False):
os.system('service yunohost-firewall start')
service_regen_conf(force=True)
logger.success(m18n.n('yunohost_configured'))
@ -298,6 +321,7 @@ def tools_update(ignore_apps=False, ignore_packages=False):
logger.info(m18n.n('updating_apt_cache'))
if not cache.update():
raise MoulinetteError(errno.EPERM, m18n.n('update_cache_failed'))
logger.info(m18n.n('done'))
cache.open(None)
@ -378,6 +402,7 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False):
critical_upgrades.add(pkg.name)
# Temporarily keep package ...
pkg.mark_keep()
# ... and set a hourly cron up to upgrade critical packages
if critical_upgrades:
logger.info(m18n.n('packages_upgrade_critical_later',
@ -387,6 +412,7 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False):
if cache.get_changes():
logger.info(m18n.n('upgrading_packages'))
try:
# Apply APT changes
# TODO: Logs output for the API
@ -394,7 +420,7 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False):
apt.progress.base.InstallProgress())
except Exception as e:
failure = True
logging.warning('unable to upgrade packages: %s' % str(e))
logger.warning('unable to upgrade packages: %s' % str(e))
logger.error(m18n.n('packages_upgrade_failed'))
else:
logger.info(m18n.n('done'))
@ -406,7 +432,7 @@ def tools_upgrade(auth, ignore_apps=False, ignore_packages=False):
app_upgrade(auth)
except Exception as e:
failure = True
logging.warning('unable to upgrade apps: %s' % str(e))
logger.warning('unable to upgrade apps: %s' % str(e))
logger.error(m18n.n('app_upgrade_failed'))
if not failure:
@ -473,6 +499,7 @@ def tools_diagnosis(auth, private=False):
# Services status
services = service_status()
diagnosis['services'] = {}
for service in services:
diagnosis['services'][service] = "%s (%s)" % (services[service]['status'], services[service]['loaded'])

View file

@ -30,11 +30,11 @@ import string
import json
import errno
import subprocess
import math
import re
from moulinette.core import MoulinetteError
from moulinette.utils.log import getActionLogger
from yunohost.service import service_status
logger = getActionLogger('yunohost.user')
@ -92,7 +92,7 @@ def user_list(auth, fields=None, filter=None, limit=None, offset=None):
def user_create(auth, username, firstname, lastname, mail, password,
mailbox_quota=0):
mailbox_quota="0"):
"""
Create user
@ -177,15 +177,20 @@ def user_create(auth, username, firstname, lastname, mail, password,
try:
with open('/etc/ssowat/conf.json.persistent') as json_conf:
ssowat_conf = json.loads(str(json_conf.read()))
except ValueError as e:
raise MoulinetteError(errno.EINVAL,
m18n.n('ssowat_persistent_conf_read_error', error=e.strerror))
except IOError:
ssowat_conf = {}
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: pass
except IOError as e:
raise MoulinetteError(errno.EPERM,
m18n.n('ssowat_persistent_conf_write_error', error=e.strerror))
if auth.add(rdn, attr_dict):
# Invalidate passwd to take user creation into account
@ -232,8 +237,10 @@ def user_delete(auth, username, purge=False):
# Update SFTP user group
memberlist = auth.search(filter='cn=sftpusers', attrs=['memberUid'])[0]['memberUid']
try: memberlist.remove(username)
except: pass
try:
memberlist.remove(username)
except:
pass
if auth.update('cn=sftpusers,ou=groups', {'memberUid': memberlist}):
if purge:
subprocess.call(['rm', '-rf', '/home/{0}'.format(username)])
@ -398,25 +405,48 @@ def user_info(auth, username):
result_dict['mail-forward'] = user['maildrop'][1:]
if 'mailuserquota' in user:
if user['mailuserquota'][0] != '0':
cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0]
userquota = subprocess.check_output(cmd,stderr=subprocess.STDOUT,
shell=True)
quotavalue = re.findall(r'\d+', userquota)
result = '%s (%s%s)' % ( _convertSize(eval(quotavalue[0])),
quotavalue[2], '%')
result_dict['mailbox-quota'] = {
'limit' : user['mailuserquota'][0],
'use' : result
}
userquota = user['mailuserquota'][0]
if isinstance(userquota, int):
userquota = str(userquota)
# Test if userquota is '0' or '0M' ( quota pattern is ^(\d+[bkMGT])|0$ )
is_limited = not re.match('0[bkMGT]?', userquota)
storage_use = '?'
if service_status("dovecot")["status"] != "running":
logger.warning(m18n.n('mailbox_used_space_dovecot_down'))
else:
result_dict['mailbox-quota'] = m18n.n('unlimit')
cmd = 'doveadm -f flow quota get -u %s' % user['uid'][0]
cmd_result = subprocess.check_output(cmd, stderr=subprocess.STDOUT,
shell=True)
# Exemple of return value for cmd:
# """Quota name=User quota Type=STORAGE Value=0 Limit=- %=0
# Quota name=User quota Type=MESSAGE Value=0 Limit=- %=0"""
has_value = re.search(r'Value=(\d+)', cmd_result)
if has_value:
storage_use = int(has_value.group(1))
storage_use = _convertSize(storage_use)
if is_limited:
has_percent = re.search(r'%=(\d+)', cmd_result)
if has_percent:
percentage = int(has_percent.group(1))
storage_use += ' (%s%%)' % percentage
result_dict['mailbox-quota'] = {
'limit': userquota if is_limited else m18n.n('unlimit'),
'use': storage_use
}
if result:
return result_dict
else:
raise MoulinetteError(167, m18n.n('user_info_failed'))
def _convertSize(num, suffix=''):
for unit in ['K', 'M', 'G', 'T', 'P', 'E', 'Z']:
if abs(num) < 1024.0:

View file

@ -424,6 +424,7 @@ def get_installed_version(*pkgnames, **kwargs):
return versions[pkgnames[0]]
return versions
def meets_version_specifier(pkgname, specifier):
"""Check if a package installed version meets specifier"""
spec = SpecifierSet(specifier)

0
src/yunohost/vendor/__init__.py vendored Normal file
View file

View file

View file

@ -0,0 +1,212 @@
#!/usr/bin/env python
import argparse
import subprocess
import json
import os
import sys
import base64
import binascii
import time
import hashlib
import re
import copy
import textwrap
import logging
try:
from urllib.request import urlopen # Python 3
except ImportError:
from urllib2 import urlopen # Python 2
#DEFAULT_CA = "https://acme-staging.api.letsencrypt.org"
DEFAULT_CA = "https://acme-v01.api.letsencrypt.org"
LOGGER = logging.getLogger(__name__)
LOGGER.addHandler(logging.StreamHandler())
LOGGER.setLevel(logging.INFO)
def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA):
# helper function base64 encode for jose spec
def _b64(b):
return base64.urlsafe_b64encode(b).decode('utf8').replace("=", "")
# parse account key to get public key
log.info("Parsing account key...")
proc = subprocess.Popen(["openssl", "rsa", "-in", account_key, "-noout", "-text"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
if proc.returncode != 0:
raise IOError("OpenSSL Error: {0}".format(err))
pub_hex, pub_exp = re.search(
r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)",
out.decode('utf8'), re.MULTILINE | re.DOTALL).groups()
pub_exp = "{0:x}".format(int(pub_exp))
pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp
header = {
"alg": "RS256",
"jwk": {
"e": _b64(binascii.unhexlify(pub_exp.encode("utf-8"))),
"kty": "RSA",
"n": _b64(binascii.unhexlify(re.sub(r"(\s|:)", "", pub_hex).encode("utf-8"))),
},
}
accountkey_json = json.dumps(header['jwk'], sort_keys=True, separators=(',', ':'))
thumbprint = _b64(hashlib.sha256(accountkey_json.encode('utf8')).digest())
# helper function make signed requests
def _send_signed_request(url, payload):
payload64 = _b64(json.dumps(payload).encode('utf8'))
protected = copy.deepcopy(header)
protected["nonce"] = urlopen(CA + "/directory").headers['Replay-Nonce']
protected64 = _b64(json.dumps(protected).encode('utf8'))
proc = subprocess.Popen(["openssl", "dgst", "-sha256", "-sign", account_key],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate("{0}.{1}".format(protected64, payload64).encode('utf8'))
if proc.returncode != 0:
raise IOError("OpenSSL Error: {0}".format(err))
data = json.dumps({
"header": header, "protected": protected64,
"payload": payload64, "signature": _b64(out),
})
try:
resp = urlopen(url, data.encode('utf8'))
return resp.getcode(), resp.read()
except IOError as e:
return getattr(e, "code", None), getattr(e, "read", e.__str__)()
# find domains
log.info("Parsing CSR...")
proc = subprocess.Popen(["openssl", "req", "-in", csr, "-noout", "-text"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = proc.communicate()
if proc.returncode != 0:
raise IOError("Error loading {0}: {1}".format(csr, err))
domains = set([])
common_name = re.search(r"Subject:.*? CN=([^\s,;/]+)", out.decode('utf8'))
if common_name is not None:
domains.add(common_name.group(1))
subject_alt_names = re.search(r"X509v3 Subject Alternative Name: \n +([^\n]+)\n", out.decode('utf8'), re.MULTILINE | re.DOTALL)
if subject_alt_names is not None:
for san in subject_alt_names.group(1).split(", "):
if san.startswith("DNS:"):
domains.add(san[4:])
# get the certificate domains and expiration
log.info("Registering account...")
code, result = _send_signed_request(CA + "/acme/new-reg", {
"resource": "new-reg",
"agreement": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf",
})
if code == 201:
log.info("Registered!")
elif code == 409:
log.info("Already registered!")
else:
raise ValueError("Error registering: {0} {1}".format(code, result))
# verify each domain
for domain in domains:
log.info("Verifying {0}...".format(domain))
# get new challenge
code, result = _send_signed_request(CA + "/acme/new-authz", {
"resource": "new-authz",
"identifier": {"type": "dns", "value": domain},
})
if code != 201:
raise ValueError("Error requesting challenges: {0} {1}".format(code, result))
# make the challenge file
challenge = [c for c in json.loads(result.decode('utf8'))['challenges'] if c['type'] == "http-01"][0]
token = re.sub(r"[^A-Za-z0-9_\-]", "_", challenge['token'])
keyauthorization = "{0}.{1}".format(token, thumbprint)
wellknown_path = os.path.join(acme_dir, token)
with open(wellknown_path, "w") as wellknown_file:
wellknown_file.write(keyauthorization)
# check that the file is in place
wellknown_url = "http://{0}/.well-known/acme-challenge/{1}".format(domain, token)
try:
resp = urlopen(wellknown_url)
resp_data = resp.read().decode('utf8').strip()
assert resp_data == keyauthorization
except (IOError, AssertionError):
os.remove(wellknown_path)
raise ValueError("Wrote file to {0}, but couldn't download {1}".format(
wellknown_path, wellknown_url))
# notify challenge are met
code, result = _send_signed_request(challenge['uri'], {
"resource": "challenge",
"keyAuthorization": keyauthorization,
})
if code != 202:
raise ValueError("Error triggering challenge: {0} {1}".format(code, result))
# wait for challenge to be verified
while True:
try:
resp = urlopen(challenge['uri'])
challenge_status = json.loads(resp.read().decode('utf8'))
except IOError as e:
raise ValueError("Error checking challenge: {0} {1}".format(
e.code, json.loads(e.read().decode('utf8'))))
if challenge_status['status'] == "pending":
time.sleep(2)
elif challenge_status['status'] == "valid":
log.info("{0} verified!".format(domain))
os.remove(wellknown_path)
break
else:
raise ValueError("{0} challenge did not pass: {1}".format(
domain, challenge_status))
# get the new certificate
log.info("Signing certificate...")
proc = subprocess.Popen(["openssl", "req", "-in", csr, "-outform", "DER"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
csr_der, err = proc.communicate()
code, result = _send_signed_request(CA + "/acme/new-cert", {
"resource": "new-cert",
"csr": _b64(csr_der),
})
if code != 201:
raise ValueError("Error signing certificate: {0} {1}".format(code, result))
# return signed certificate!
log.info("Certificate signed!")
return """-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format(
"\n".join(textwrap.wrap(base64.b64encode(result).decode('utf8'), 64)))
def main(argv):
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description=textwrap.dedent("""\
This script automates the process of getting a signed TLS certificate from
Let's Encrypt using the ACME protocol. It will need to be run on your server
and have access to your private account key, so PLEASE READ THROUGH IT! It's
only ~200 lines, so it won't take long.
===Example Usage===
python acme_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir /usr/share/nginx/html/.well-known/acme-challenge/ > signed.crt
===================
===Example Crontab Renewal (once per month)===
0 0 1 * * python /path/to/acme_tiny.py --account-key /path/to/account.key --csr /path/to/domain.csr --acme-dir /usr/share/nginx/html/.well-known/acme-challenge/ > /path/to/signed.crt 2>> /var/log/acme_tiny.log
==============================================
""")
)
parser.add_argument("--account-key", required=True, help="path to your Let's Encrypt account private key")
parser.add_argument("--csr", required=True, help="path to your certificate signing request")
parser.add_argument("--acme-dir", required=True, help="path to the .well-known/acme-challenge/ directory")
parser.add_argument("--quiet", action="store_const", const=logging.ERROR, help="suppress output except for errors")
parser.add_argument("--ca", default=DEFAULT_CA, help="certificate authority, default is Let's Encrypt")
args = parser.parse_args(argv)
LOGGER.setLevel(args.quiet or LOGGER.level)
signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, log=LOGGER, CA=args.ca)
sys.stdout.write(signed_crt)
if __name__ == "__main__": # pragma: no cover
main(sys.argv[1:])

4
tests/test_actionmap.py Normal file
View file

@ -0,0 +1,4 @@
import yaml
def test_yaml_syntax():
yaml.load(open("data/actionsmap/yunohost.yml"))