mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge branch 'unstable' into tsig-sha256
This commit is contained in:
commit
34433f07a4
17 changed files with 416 additions and 171 deletions
|
@ -6,7 +6,7 @@
|
||||||
#
|
#
|
||||||
# If no argument provided, a standard directory will be use. /var/log/${app}
|
# If no argument provided, a standard directory will be use. /var/log/${app}
|
||||||
# You can provide a path with the directory only or with the logfile.
|
# You can provide a path with the directory only or with the logfile.
|
||||||
# /parentdir/logdir/
|
# /parentdir/logdir
|
||||||
# /parentdir/logdir/logfile.log
|
# /parentdir/logdir/logfile.log
|
||||||
#
|
#
|
||||||
# It's possible to use this helper several times, each config will be added to the same logrotate config file.
|
# It's possible to use this helper several times, each config will be added to the same logrotate config file.
|
||||||
|
@ -24,7 +24,7 @@ ynh_use_logrotate () {
|
||||||
if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile
|
if [ "$(echo ${1##*.})" == "log" ]; then # Keep only the extension to check if it's a logfile
|
||||||
logfile=$1 # In this case, focus logrotate on the logfile
|
logfile=$1 # In this case, focus logrotate on the logfile
|
||||||
else
|
else
|
||||||
logfile=$1/.log # Else, uses the directory and all logfile into it.
|
logfile=$1/*.log # Else, uses the directory and all logfile into it.
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log
|
logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log
|
||||||
|
@ -123,6 +123,9 @@ ynh_add_nginx_config () {
|
||||||
# To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable.
|
# To avoid a break by set -u, use a void substitution ${var:-}. If the variable is not set, it's simply set with an empty variable.
|
||||||
# Substitute in a nginx config file only if the variable is not empty
|
# Substitute in a nginx config file only if the variable is not empty
|
||||||
if test -n "${path_url:-}"; then
|
if test -n "${path_url:-}"; then
|
||||||
|
# path_url_slash_less is path_url, or a blank value if path_url is only '/'
|
||||||
|
path_url_slash_less=${path_url%/}
|
||||||
|
ynh_replace_string "__PATH__/" "$path_url_slash_less/" "$finalnginxconf"
|
||||||
ynh_replace_string "__PATH__" "$path_url" "$finalnginxconf"
|
ynh_replace_string "__PATH__" "$path_url" "$finalnginxconf"
|
||||||
fi
|
fi
|
||||||
if test -n "${domain:-}"; then
|
if test -n "${domain:-}"; then
|
||||||
|
|
|
@ -73,7 +73,7 @@ ynh_mysql_drop_db() {
|
||||||
# | arg: db - the database name to dump
|
# | arg: db - the database name to dump
|
||||||
# | ret: the mysqldump output
|
# | ret: the mysqldump output
|
||||||
ynh_mysql_dump_db() {
|
ynh_mysql_dump_db() {
|
||||||
mysqldump -u "root" -p"$(sudo cat $MYSQL_ROOT_PWD_FILE)" "$1"
|
mysqldump -u "root" -p"$(sudo cat $MYSQL_ROOT_PWD_FILE)" --single-transaction --skip-dump-date "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create a user
|
# Create a user
|
||||||
|
|
|
@ -126,10 +126,7 @@ ynh_install_app_dependencies () {
|
||||||
version=$(grep '\"version\": ' "$manifest_path" | cut -d '"' -f 4) # Retrieve the version number in the manifest file.
|
version=$(grep '\"version\": ' "$manifest_path" | cut -d '"' -f 4) # Retrieve the version number in the manifest file.
|
||||||
dep_app=${app//_/-} # Replace all '_' by '-'
|
dep_app=${app//_/-} # Replace all '_' by '-'
|
||||||
|
|
||||||
if ynh_package_is_installed "${dep_app}-ynh-deps"; then
|
cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build
|
||||||
echo "A package named ${dep_app}-ynh-deps is already installed" >&2
|
|
||||||
else
|
|
||||||
cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build
|
|
||||||
Section: misc
|
Section: misc
|
||||||
Priority: optional
|
Priority: optional
|
||||||
Package: ${dep_app}-ynh-deps
|
Package: ${dep_app}-ynh-deps
|
||||||
|
@ -139,11 +136,10 @@ Architecture: all
|
||||||
Description: Fake package for ${app} (YunoHost app) dependencies
|
Description: Fake package for ${app} (YunoHost app) dependencies
|
||||||
This meta-package is only responsible of installing its dependencies.
|
This meta-package is only responsible of installing its dependencies.
|
||||||
EOF
|
EOF
|
||||||
ynh_package_install_from_equivs /tmp/${dep_app}-ynh-deps.control \
|
ynh_package_install_from_equivs /tmp/${dep_app}-ynh-deps.control \
|
||||||
|| ynh_die "Unable to install dependencies" # Install the fake package and its dependencies
|
|| ynh_die "Unable to install dependencies" # Install the fake package and its dependencies
|
||||||
rm /tmp/${dep_app}-ynh-deps.control
|
rm /tmp/${dep_app}-ynh-deps.control
|
||||||
ynh_app_setting_set $app apt_dependencies $dependencies
|
ynh_app_setting_set $app apt_dependencies $dependencies
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Remove fake package and its dependencies
|
# Remove fake package and its dependencies
|
||||||
|
|
|
@ -10,17 +10,50 @@ ynh_string_random() {
|
||||||
| sed -n 's/\(.\{'"${1:-24}"'\}\).*/\1/p'
|
| sed -n 's/\(.\{'"${1:-24}"'\}\).*/\1/p'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Substitute/replace a string by another in a file
|
# Substitute/replace a string (or expression) by another in a file
|
||||||
#
|
#
|
||||||
# usage: ynh_replace_string match_string replace_string target_file
|
# usage: ynh_replace_string match_string replace_string target_file
|
||||||
# | arg: match_string - String to be searched and replaced in the file
|
# | arg: match_string - String to be searched and replaced in the file
|
||||||
# | arg: replace_string - String that will replace matches
|
# | arg: replace_string - String that will replace matches
|
||||||
# | arg: target_file - File in which the string will be replaced.
|
# | arg: target_file - File in which the string will be replaced.
|
||||||
|
#
|
||||||
|
# As this helper is based on sed command, regular expressions and
|
||||||
|
# references to sub-expressions can be used
|
||||||
|
# (see sed manual page for more information)
|
||||||
ynh_replace_string () {
|
ynh_replace_string () {
|
||||||
delimit=@
|
local delimit=@
|
||||||
match_string=${1//${delimit}/"\\${delimit}"} # Escape the delimiter if it's in the string.
|
local match_string=$1
|
||||||
replace_string=${2//${delimit}/"\\${delimit}"}
|
local replace_string=$2
|
||||||
workfile=$3
|
local workfile=$3
|
||||||
|
|
||||||
|
# Escape the delimiter if it's in the string.
|
||||||
|
match_string=${match_string//${delimit}/"\\${delimit}"}
|
||||||
|
replace_string=${replace_string//${delimit}/"\\${delimit}"}
|
||||||
|
|
||||||
sudo sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$workfile"
|
sudo sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$workfile"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Substitute/replace a special string by another in a file
|
||||||
|
#
|
||||||
|
# usage: ynh_replace_special_string match_string replace_string target_file
|
||||||
|
# | arg: match_string - String to be searched and replaced in the file
|
||||||
|
# | arg: replace_string - String that will replace matches
|
||||||
|
# | arg: target_file - File in which the string will be replaced.
|
||||||
|
#
|
||||||
|
# This helper will use ynh_replace_string, but as you can use special
|
||||||
|
# characters, you can't use some regular expressions and sub-expressions.
|
||||||
|
ynh_replace_special_string () {
|
||||||
|
local match_string=$1
|
||||||
|
local replace_string=$2
|
||||||
|
local workfile=$3
|
||||||
|
|
||||||
|
# Escape any backslash to preserve them as simple backslash.
|
||||||
|
match_string=${match_string//\\/"\\\\"}
|
||||||
|
replace_string=${replace_string//\\/"\\\\"}
|
||||||
|
|
||||||
|
# Escape the & character, who has a special function in sed.
|
||||||
|
match_string=${match_string//&/"\&"}
|
||||||
|
replace_string=${replace_string//&/"\&"}
|
||||||
|
|
||||||
|
ynh_replace_string "$match_string" "$replace_string" "$workfile"
|
||||||
|
}
|
||||||
|
|
|
@ -59,14 +59,19 @@ ynh_restore_upgradebackup () {
|
||||||
# ynh_abort_if_errors
|
# ynh_abort_if_errors
|
||||||
#
|
#
|
||||||
ynh_backup_before_upgrade () {
|
ynh_backup_before_upgrade () {
|
||||||
|
if [ ! -e "/etc/yunohost/apps/$app/scripts/backup" ]
|
||||||
|
then
|
||||||
|
echo "This app doesn't have any backup script." >&2
|
||||||
|
return
|
||||||
|
fi
|
||||||
backup_number=1
|
backup_number=1
|
||||||
old_backup_number=2
|
old_backup_number=2
|
||||||
app_bck=${app//_/-} # Replace all '_' by '-'
|
app_bck=${app//_/-} # Replace all '_' by '-'
|
||||||
|
|
||||||
# Check if a backup already exists with the prefix 1
|
# Check if a backup already exists with the prefix 1
|
||||||
if sudo yunohost backup list | grep -q $app_bck-pre-upgrade1
|
if sudo yunohost backup list | grep -q $app_bck-pre-upgrade1
|
||||||
then
|
then
|
||||||
# Prefix becomes 2 to preserve the previous backup
|
# Prefix becomes 2 to preserve the previous backup
|
||||||
backup_number=2
|
backup_number=2
|
||||||
old_backup_number=1
|
old_backup_number=1
|
||||||
fi
|
fi
|
||||||
|
@ -74,7 +79,7 @@ ynh_backup_before_upgrade () {
|
||||||
# Create backup
|
# Create backup
|
||||||
sudo yunohost backup create --ignore-system --apps $app --name $app_bck-pre-upgrade$backup_number
|
sudo yunohost backup create --ignore-system --apps $app --name $app_bck-pre-upgrade$backup_number
|
||||||
if [ "$?" -eq 0 ]
|
if [ "$?" -eq 0 ]
|
||||||
then
|
then
|
||||||
# If the backup succeeded, remove the previous backup
|
# If the backup succeeded, remove the previous backup
|
||||||
if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$old_backup_number
|
if sudo yunohost backup list | grep -q $app_bck-pre-upgrade$old_backup_number
|
||||||
then
|
then
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
uPnP:
|
uPnP:
|
||||||
enabled: false
|
enabled: false
|
||||||
TCP: [22, 25, 53, 80, 443, 465, 587, 993, 5222, 5269]
|
TCP: [22, 25, 80, 443, 465, 587, 993, 5222, 5269]
|
||||||
UDP: [53]
|
UDP: []
|
||||||
ipv4:
|
ipv4:
|
||||||
TCP: [22, 25, 53, 80, 443, 465, 587, 993, 5222, 5269]
|
TCP: [22, 25, 53, 80, 443, 465, 587, 993, 5222, 5269]
|
||||||
UDP: [53, 5353]
|
UDP: [53, 5353]
|
||||||
|
|
|
@ -47,6 +47,7 @@ yunohost-api:
|
||||||
log: /var/log/yunohost/yunohost-api.log
|
log: /var/log/yunohost/yunohost-api.log
|
||||||
yunohost-firewall:
|
yunohost-firewall:
|
||||||
status: service
|
status: service
|
||||||
|
need_lock: true
|
||||||
nslcd:
|
nslcd:
|
||||||
status: service
|
status: service
|
||||||
log: /var/log/syslog
|
log: /var/log/syslog
|
||||||
|
|
45
debian/changelog
vendored
45
debian/changelog
vendored
|
@ -1,3 +1,48 @@
|
||||||
|
yunohost (2.7.5) stable; urgency=low
|
||||||
|
|
||||||
|
(Bumping version number for stable release)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Sat, 02 Dec 2017 12:38:00 -0500
|
||||||
|
|
||||||
|
yunohost (2.7.4) testing; urgency=low
|
||||||
|
|
||||||
|
* [fix] Update acme-tiny as LE updated its ToS (#386)
|
||||||
|
* [fix] Fix helper for old apps without backup script (#388)
|
||||||
|
* [mod] Remove port 53 from UPnP (but keep it open on local network) (#362)
|
||||||
|
* [i18n] Improve French translation
|
||||||
|
|
||||||
|
Thanks to all contributors <3 ! (jibec, Moul, Maniack, Aleks)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Tue, 28 Nov 2017 19:01:41 -0500
|
||||||
|
|
||||||
|
yunohost (2.7.3) testing; urgency=low
|
||||||
|
|
||||||
|
Major changes :
|
||||||
|
|
||||||
|
* [fix] Refactor/clean madness related to DynDNS (#353)
|
||||||
|
* [i18n] Improve french translation (#355)
|
||||||
|
* [fix] Use cryptorandom to generate password (#358)
|
||||||
|
* [enh] Support for single app upgrade from the webadmin (#359)
|
||||||
|
* [enh] Be able to give lock to son processes detached by systemctl (#367)
|
||||||
|
* [enh] Make MySQL dumps with a single transaction to ensure backup consistency (#370)
|
||||||
|
|
||||||
|
Misc fixes/improvements :
|
||||||
|
|
||||||
|
* [enh] Escape some special character in ynh_replace_string (#354)
|
||||||
|
* [fix] Allow dash at the beginning of app settings value (#357)
|
||||||
|
* [enh] Handle root path in nginx conf (#361)
|
||||||
|
* [enh] Add debugging in ldap init (#365)
|
||||||
|
* [fix] Fix app_upgrade_string with missing key
|
||||||
|
* [fix] Fix for change_url path normalizing with root url (#368)
|
||||||
|
* [fix] Missing 'ask_path' string (#369)
|
||||||
|
* [enh] Remove date from sql dump (#371)
|
||||||
|
* [fix] Fix unicode error in backup/restore (#375)
|
||||||
|
* [fix] Fix an error in ynh_replace_string (#379)
|
||||||
|
|
||||||
|
Thanks to all contributors <3 ! (Bram, Maniack C, ljf, JimboJoe, ariasuni, Jibec, Aleks)
|
||||||
|
|
||||||
|
-- Alexandre Aubin <alex.aubin@mailoo.org> Thu, 12 Oct 2017 16:18:51 -0400
|
||||||
|
|
||||||
yunohost (2.7.2) stable; urgency=low
|
yunohost (2.7.2) stable; urgency=low
|
||||||
|
|
||||||
* [mod] pep8
|
* [mod] pep8
|
||||||
|
|
|
@ -55,6 +55,7 @@
|
||||||
"ask_main_domain": "Main domain",
|
"ask_main_domain": "Main domain",
|
||||||
"ask_new_admin_password": "New administration password",
|
"ask_new_admin_password": "New administration password",
|
||||||
"ask_password": "Password",
|
"ask_password": "Password",
|
||||||
|
"ask_path": "Path",
|
||||||
"backup_abstract_method": "This backup method hasn't yet been implemented",
|
"backup_abstract_method": "This backup method hasn't yet been implemented",
|
||||||
"backup_action_required": "You must specify something to save",
|
"backup_action_required": "You must specify something to save",
|
||||||
"backup_app_failed": "Unable to back up the app '{app:s}'",
|
"backup_app_failed": "Unable to back up the app '{app:s}'",
|
||||||
|
@ -157,6 +158,7 @@
|
||||||
"domains_available": "Available domains:",
|
"domains_available": "Available domains:",
|
||||||
"done": "Done",
|
"done": "Done",
|
||||||
"downloading": "Downloading...",
|
"downloading": "Downloading...",
|
||||||
|
"dyndns_could_not_check_provide": "Could not check if {provider:s} can provide {domain:s}.",
|
||||||
"dyndns_cron_installed": "The DynDNS cron job has been installed",
|
"dyndns_cron_installed": "The DynDNS cron job has been installed",
|
||||||
"dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job",
|
"dyndns_cron_remove_failed": "Unable to remove the DynDNS cron job",
|
||||||
"dyndns_cron_removed": "The DynDNS cron job has been removed",
|
"dyndns_cron_removed": "The DynDNS cron job has been removed",
|
||||||
|
@ -167,7 +169,8 @@
|
||||||
"dyndns_no_domain_registered": "No domain has been registered with DynDNS",
|
"dyndns_no_domain_registered": "No domain has been registered with DynDNS",
|
||||||
"dyndns_registered": "The DynDNS domain has been registered",
|
"dyndns_registered": "The DynDNS domain has been registered",
|
||||||
"dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}",
|
"dyndns_registration_failed": "Unable to register DynDNS domain: {error:s}",
|
||||||
"dyndns_unavailable": "Unavailable DynDNS subdomain",
|
"dyndns_domain_not_provided": "Dyndns provider {provider:s} cannot provide domain {domain:s}.",
|
||||||
|
"dyndns_unavailable": "Domain {domain:s} is not available.",
|
||||||
"executing_command": "Executing command '{command:s}'...",
|
"executing_command": "Executing command '{command:s}'...",
|
||||||
"executing_script": "Executing script '{script:s}'...",
|
"executing_script": "Executing script '{script:s}'...",
|
||||||
"extracting": "Extracting...",
|
"extracting": "Extracting...",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"action_invalid": "Action « {action:s} » incorrecte",
|
"action_invalid": "Action « {action:s} » incorrecte",
|
||||||
"admin_password": "Mot de passe d'administration",
|
"admin_password": "Mot de passe d'administration",
|
||||||
"admin_password_change_failed": "Impossible de modifier le mot de passe d'administration",
|
"admin_password_change_failed": "Impossible de changer le mot de passe",
|
||||||
"admin_password_changed": "Le mot de passe d'administration a été modifié",
|
"admin_password_changed": "Le mot de passe d'administration a été modifié",
|
||||||
"app_already_installed": "{app:s} est déjà installé",
|
"app_already_installed": "{app:s} est déjà installé",
|
||||||
"app_argument_choice_invalid": "Choix invalide pour le paramètre « {name:s} », il doit être l'un de {choices:s}",
|
"app_argument_choice_invalid": "Choix invalide pour le paramètre « {name:s} », il doit être l'un de {choices:s}",
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
"app_install_files_invalid": "Fichiers d'installation incorrects",
|
"app_install_files_invalid": "Fichiers d'installation incorrects",
|
||||||
"app_location_already_used": "Une application est déjà installée à cet emplacement",
|
"app_location_already_used": "Une application est déjà installée à cet emplacement",
|
||||||
"app_location_install_failed": "Impossible d'installer l'application à cet emplacement",
|
"app_location_install_failed": "Impossible d'installer l'application à cet emplacement",
|
||||||
"app_manifest_invalid": "Manifeste d'application incorrect",
|
"app_manifest_invalid": "Manifeste d'application incorrect : {error}",
|
||||||
"app_no_upgrade": "Aucune application à mettre à jour",
|
"app_no_upgrade": "Aucune application à mettre à jour",
|
||||||
"app_not_correctly_installed": "{app:s} semble être mal installé",
|
"app_not_correctly_installed": "{app:s} semble être mal installé",
|
||||||
"app_not_installed": "{app:s} n'est pas installé",
|
"app_not_installed": "{app:s} n'est pas installé",
|
||||||
|
@ -98,7 +98,7 @@
|
||||||
"dyndns_no_domain_registered": "Aucun domaine n'a été enregistré avec DynDNS",
|
"dyndns_no_domain_registered": "Aucun domaine n'a été enregistré avec DynDNS",
|
||||||
"dyndns_registered": "Le domaine DynDNS a été enregistré",
|
"dyndns_registered": "Le domaine DynDNS a été enregistré",
|
||||||
"dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {error:s}",
|
"dyndns_registration_failed": "Impossible d'enregistrer le domaine DynDNS : {error:s}",
|
||||||
"dyndns_unavailable": "Sous-domaine DynDNS indisponible",
|
"dyndns_unavailable": "Le domaine {domain:s} est indisponible.",
|
||||||
"executing_command": "Exécution de la commande « {command:s} »...",
|
"executing_command": "Exécution de la commande « {command:s} »...",
|
||||||
"executing_script": "Exécution du script « {script:s} »...",
|
"executing_script": "Exécution du script « {script:s} »...",
|
||||||
"extracting": "Extraction...",
|
"extracting": "Extraction...",
|
||||||
|
@ -320,7 +320,7 @@
|
||||||
"backup_archive_system_part_not_available": "La partie « {part:s} » du système n’est pas disponible dans cette sauvegarde",
|
"backup_archive_system_part_not_available": "La partie « {part:s} » du système n’est pas disponible dans cette sauvegarde",
|
||||||
"backup_archive_mount_failed": "Le montage de l’archive de sauvegarde a échoué",
|
"backup_archive_mount_failed": "Le montage de l’archive de sauvegarde a échoué",
|
||||||
"backup_archive_writing_error": "Impossible d’ajouter les fichiers à la sauvegarde dans l’archive compressée",
|
"backup_archive_writing_error": "Impossible d’ajouter les fichiers à la sauvegarde dans l’archive compressée",
|
||||||
"backup_ask_for_copying_if_needed": "Votre système ne prend pas complètement en charge la méthode rapide d’organisation des fichiers dans l’archive, voulez-vous les organiser en copiant {size:s} Mio ?",
|
"backup_ask_for_copying_if_needed": "Certains fichiers n’ont pas pu être préparés pour être sauvegardée en utilisant la méthode qui évite de temporairement gaspiller de l’espace sur le système. Pour mener la sauvegarde, {size:s} Mio doivent être temporairement utilisés. Acceptez-vous ?",
|
||||||
"backup_borg_not_implemented": "La méthode de sauvegarde Bord n’est pas encore implémentée",
|
"backup_borg_not_implemented": "La méthode de sauvegarde Bord n’est pas encore implémentée",
|
||||||
"backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée",
|
"backup_cant_mount_uncompress_archive": "Impossible de monter en lecture seule le dossier de l’archive décompressée",
|
||||||
"backup_copying_to_organize_the_archive": "Copie de {size:s} Mio pour organiser l’archive",
|
"backup_copying_to_organize_the_archive": "Copie de {size:s} Mio pour organiser l’archive",
|
||||||
|
@ -344,5 +344,28 @@
|
||||||
"restore_mounting_archive": "Montage de l’archive dans « {path:s} »",
|
"restore_mounting_archive": "Montage de l’archive dans « {path:s} »",
|
||||||
"restore_may_be_not_enough_disk_space": "Votre système semble ne pas avoir suffisamment d’espace disponible (libre : {free_space:d} octets, nécessaire : {needed_space:d} octets, marge de sécurité : {margin:d} octets)",
|
"restore_may_be_not_enough_disk_space": "Votre système semble ne pas avoir suffisamment d’espace disponible (libre : {free_space:d} octets, nécessaire : {needed_space:d} octets, marge de sécurité : {margin:d} octets)",
|
||||||
"restore_not_enough_disk_space": "Espace disponible insuffisant (libre : {free_space:d} octets, nécessaire : {needed_space:d} octets, marge de sécurité : {margin:d} octets)",
|
"restore_not_enough_disk_space": "Espace disponible insuffisant (libre : {free_space:d} octets, nécessaire : {needed_space:d} octets, marge de sécurité : {margin:d} octets)",
|
||||||
"restore_system_part_failed": "Impossible de restaurer la partie « {part:s} » du système"
|
"restore_system_part_failed": "Impossible de restaurer la partie « {part:s} » du système",
|
||||||
|
"backup_couldnt_bind": "Impossible de lier {src:s} avec {dest:s}.",
|
||||||
|
"domain_dns_conf_is_just_a_recommendation": "Cette page montre la configuration *recommandée*. Elle ne configure *pas* le DNS pour vous. Il est de votre responsabilité que de configurer votre zone DNS chez votre registrar DNS avec cette recommandation.",
|
||||||
|
"domain_dyndns_dynette_is_unreachable": "Impossible de contacter la dynette YunoHost, soit YunoHost n’est pas correctement connecté à internet ou alors le serveur de dynette est arrêté. Erreur : {error}",
|
||||||
|
"migrations_backward": "Migration en arrière.",
|
||||||
|
"migrations_bad_value_for_target": "Nombre invalide pour le paramètre « target », les numéros de migration sont ou {}",
|
||||||
|
"migrations_cant_reach_migration_file": "Impossible d’accéder aux fichiers de migrations avec le chemin %s",
|
||||||
|
"migrations_current_target": "La cible de migration est {}",
|
||||||
|
"migrations_error_failed_to_load_migration": "ERREUR : échec du chargement de migration {number} {name}",
|
||||||
|
"migrations_forward": "Migration en avant",
|
||||||
|
"migrations_loading_migration": "Chargement de la migration {number} {name}…",
|
||||||
|
"migrations_migration_has_failed": "La migration {number} {name} a échoué avec l’exception {exception}, annulation",
|
||||||
|
"migrations_no_migrations_to_run": "Aucune migration à lancer",
|
||||||
|
"migrations_show_currently_running_migration": "Application de la migration {number} {name}…",
|
||||||
|
"migrations_show_last_migration": "La dernière migration appliquée est {}",
|
||||||
|
"migrations_skip_migration": "Omission de la migration {number} {name}…",
|
||||||
|
"server_shutdown": "Le serveur sera éteint",
|
||||||
|
"server_shutdown_confirm": "Le serveur immédiatement être éteint, le voulez-vous vraiment ? [{answers:s}]",
|
||||||
|
"server_reboot": "Le serveur va redémarrer",
|
||||||
|
"server_reboot_confirm": "Le serveur va redémarrer immédiatement, le voulez-vous vraiment ? [{answers:s}]",
|
||||||
|
"app_upgrade_some_app_failed": "Impossible de mettre à jour certaines applications",
|
||||||
|
"ask_path": "Chemin",
|
||||||
|
"dyndns_could_not_check_provide": "Impossible de vérifier si {provider:s} peut fournir {domain:s}.",
|
||||||
|
"dyndns_domain_not_provided": "Le fournisseur Dyndns {provider:s} ne peut pas fournir le domaine {domain:s}."
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ import pwd
|
||||||
import grp
|
import grp
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from moulinette import msignals, m18n
|
from moulinette import msignals, m18n, msettings
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
|
|
||||||
|
@ -445,8 +445,9 @@ def app_change_url(auth, app, domain, path):
|
||||||
|
|
||||||
# Normalize path and domain format
|
# Normalize path and domain format
|
||||||
domain = domain.strip().lower()
|
domain = domain.strip().lower()
|
||||||
old_path = '/' + old_path.strip("/").strip() + '/'
|
|
||||||
path = '/' + path.strip("/").strip() + '/'
|
old_path = normalize_url_path(old_path)
|
||||||
|
path = normalize_url_path(path)
|
||||||
|
|
||||||
if (domain, path) == (old_domain, old_path):
|
if (domain, path) == (old_domain, old_path):
|
||||||
raise MoulinetteError(errno.EINVAL, m18n.n("app_change_url_identical_domains", domain=domain, path=path))
|
raise MoulinetteError(errno.EINVAL, m18n.n("app_change_url_identical_domains", domain=domain, path=path))
|
||||||
|
@ -531,6 +532,9 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
||||||
"""
|
"""
|
||||||
from yunohost.hook import hook_add, hook_remove, hook_exec
|
from yunohost.hook import hook_add, hook_remove, hook_exec
|
||||||
|
|
||||||
|
# Retrieve interface
|
||||||
|
is_api = msettings.get('interface') == 'api'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
app_list()
|
app_list()
|
||||||
except MoulinetteError:
|
except MoulinetteError:
|
||||||
|
@ -632,6 +636,10 @@ def app_upgrade(auth, app=[], url=None, file=None):
|
||||||
|
|
||||||
logger.success(m18n.n('upgrade_complete'))
|
logger.success(m18n.n('upgrade_complete'))
|
||||||
|
|
||||||
|
# Return API logs if it is an API call
|
||||||
|
if is_api:
|
||||||
|
return {"log": service_log('yunohost-api', number="100").values()[0]}
|
||||||
|
|
||||||
|
|
||||||
def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
|
def app_install(auth, app, label=None, args=None, no_remove_on_failure=False):
|
||||||
"""
|
"""
|
||||||
|
@ -2105,3 +2113,10 @@ def random_password(length=8):
|
||||||
|
|
||||||
char_set = string.ascii_uppercase + string.digits + string.ascii_lowercase
|
char_set = string.ascii_uppercase + string.digits + string.ascii_lowercase
|
||||||
return ''.join([random.SystemRandom().choice(char_set) for x in range(length)])
|
return ''.join([random.SystemRandom().choice(char_set) for x in range(length)])
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_url_path(url_path):
|
||||||
|
if url_path.strip("/").strip():
|
||||||
|
return '/' + url_path.strip("/").strip() + '/'
|
||||||
|
|
||||||
|
return "/"
|
||||||
|
|
|
@ -166,11 +166,11 @@ class BackupRestoreTargetsManager(object):
|
||||||
or (exclude and isinstance(exclude, list) and not include)
|
or (exclude and isinstance(exclude, list) and not include)
|
||||||
|
|
||||||
if include:
|
if include:
|
||||||
return [target for target in self.targets[category]
|
return [target.encode("Utf-8") for target in self.targets[category]
|
||||||
if self.results[category][target] in include]
|
if self.results[category][target] in include]
|
||||||
|
|
||||||
if exclude:
|
if exclude:
|
||||||
return [target for target in self.targets[category]
|
return [target.encode("Utf-8") for target in self.targets[category]
|
||||||
if self.results[category][target] not in exclude]
|
if self.results[category][target] not in exclude]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -82,28 +82,23 @@ def domain_add(auth, domain, dyndns=False):
|
||||||
|
|
||||||
# DynDNS domain
|
# DynDNS domain
|
||||||
if dyndns:
|
if dyndns:
|
||||||
if len(domain.split('.')) < 3:
|
|
||||||
raise MoulinetteError(errno.EINVAL, m18n.n('domain_dyndns_invalid'))
|
|
||||||
|
|
||||||
|
# Do not allow to subscribe to multiple dyndns domains...
|
||||||
if os.path.exists('/etc/cron.d/yunohost-dyndns'):
|
if os.path.exists('/etc/cron.d/yunohost-dyndns'):
|
||||||
raise MoulinetteError(errno.EPERM,
|
raise MoulinetteError(errno.EPERM,
|
||||||
m18n.n('domain_dyndns_already_subscribed'))
|
m18n.n('domain_dyndns_already_subscribed'))
|
||||||
try:
|
|
||||||
r = requests.get('https://dyndns.yunohost.org/domains', timeout=30)
|
|
||||||
except requests.ConnectionError as e:
|
|
||||||
raise MoulinetteError(errno.EHOSTUNREACH,
|
|
||||||
m18n.n('domain_dyndns_dynette_is_unreachable', error=str(e)))
|
|
||||||
|
|
||||||
from yunohost.dyndns import dyndns_subscribe
|
from yunohost.dyndns import dyndns_subscribe, _dyndns_provides
|
||||||
|
|
||||||
dyndomains = json.loads(r.text)
|
# Check that this domain can effectively be provided by
|
||||||
dyndomain = '.'.join(domain.split('.')[1:])
|
# dyndns.yunohost.org. (i.e. is it a nohost.me / noho.st)
|
||||||
if dyndomain in dyndomains:
|
if not _dyndns_provides("dyndns.yunohost.org", domain):
|
||||||
dyndns_subscribe(domain=domain)
|
|
||||||
else:
|
|
||||||
raise MoulinetteError(errno.EINVAL,
|
raise MoulinetteError(errno.EINVAL,
|
||||||
m18n.n('domain_dyndns_root_unknown'))
|
m18n.n('domain_dyndns_root_unknown'))
|
||||||
|
|
||||||
|
# Actually subscribe
|
||||||
|
dyndns_subscribe(domain=domain)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
yunohost.certificate._certificate_install_selfsigned([domain], False)
|
yunohost.certificate._certificate_install_selfsigned([domain], False)
|
||||||
|
|
||||||
|
@ -281,6 +276,25 @@ def get_public_ip(protocol=4):
|
||||||
raise MoulinetteError(errno.ENETUNREACH,
|
raise MoulinetteError(errno.ENETUNREACH,
|
||||||
m18n.n('no_internet_connection'))
|
m18n.n('no_internet_connection'))
|
||||||
|
|
||||||
|
def get_public_ips():
|
||||||
|
"""
|
||||||
|
Retrieve the public IPv4 and v6 from ip. and ip6.yunohost.org
|
||||||
|
|
||||||
|
Returns a 2-tuple (ipv4, ipv6). ipv4 or ipv6 can be None if they were not
|
||||||
|
found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
ipv4 = get_public_ip()
|
||||||
|
except:
|
||||||
|
ipv4 = None
|
||||||
|
try:
|
||||||
|
ipv6 = get_public_ip(6)
|
||||||
|
except:
|
||||||
|
ipv6 = None
|
||||||
|
|
||||||
|
return (ipv4, ipv6)
|
||||||
|
|
||||||
|
|
||||||
def _get_maindomain():
|
def _get_maindomain():
|
||||||
with open('/etc/yunohost/current_host', 'r') as f:
|
with open('/etc/yunohost/current_host', 'r') as f:
|
||||||
|
|
|
@ -36,45 +36,82 @@ import subprocess
|
||||||
from moulinette import m18n
|
from moulinette import m18n
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
|
from moulinette.utils.filesystem import read_file, write_to_file, rm
|
||||||
|
from moulinette.utils.network import download_json
|
||||||
|
|
||||||
from yunohost.domain import get_public_ip, _get_maindomain, _build_dns_conf
|
from yunohost.domain import get_public_ips, _get_maindomain, _build_dns_conf
|
||||||
|
|
||||||
logger = getActionLogger('yunohost.dyndns')
|
logger = getActionLogger('yunohost.dyndns')
|
||||||
|
|
||||||
|
OLD_IPV4_FILE = '/etc/yunohost/dyndns/old_ip'
|
||||||
class IPRouteLine(object):
|
OLD_IPV6_FILE = '/etc/yunohost/dyndns/old_ipv6'
|
||||||
""" Utility class to parse an ip route output line
|
DYNDNS_ZONE = '/etc/yunohost/dyndns/zone'
|
||||||
|
|
||||||
The output of ip ro is variable and hard to parse completly, it would
|
|
||||||
require a real parser, not just a regexp, so do minimal parsing here...
|
|
||||||
|
|
||||||
>>> a = IPRouteLine('2001:: from :: via fe80::c23f:fe:1e:cafe dev eth0 src 2000:de:beef:ca:0:fe:1e:cafe metric 0')
|
|
||||||
>>> a.src_addr
|
|
||||||
"2000:de:beef:ca:0:fe:1e:cafe"
|
|
||||||
"""
|
|
||||||
regexp = re.compile(
|
|
||||||
r'(?P<unreachable>unreachable)?.*src\s+(?P<src_addr>[0-9a-f:]+).*')
|
|
||||||
|
|
||||||
def __init__(self, line):
|
|
||||||
self.m = self.regexp.match(line)
|
|
||||||
if not self.m:
|
|
||||||
raise ValueError("Not a valid ip route get line")
|
|
||||||
|
|
||||||
# make regexp group available as object attributes
|
|
||||||
for k, v in self.m.groupdict().items():
|
|
||||||
setattr(self, k, v)
|
|
||||||
|
|
||||||
|
|
||||||
RE_DYNDNS_PRIVATE_KEY_MD5 = re.compile(
|
RE_DYNDNS_PRIVATE_KEY_MD5 = re.compile(
|
||||||
r'.*/K(?P<domain>[^\s\+]+)\.\+157.+\.private$'
|
r'.*/K(?P<domain>[^\s\+]+)\.\+157.+\.private$'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
RE_DYNDNS_PRIVATE_KEY_SHA512 = re.compile(
|
RE_DYNDNS_PRIVATE_KEY_SHA512 = re.compile(
|
||||||
r'.*/K(?P<domain>[^\s\+]+)\.\+165.+\.private$'
|
r'.*/K(?P<domain>[^\s\+]+)\.\+165.+\.private$'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _dyndns_provides(provider, domain):
|
||||||
|
"""
|
||||||
|
Checks if a provider provide/manage a given domain.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
provider -- The url of the provider, e.g. "dyndns.yunohost.org"
|
||||||
|
domain -- The full domain that you'd like.. e.g. "foo.nohost.me"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the provider provide/manages the domain. False otherwise.
|
||||||
|
"""
|
||||||
|
|
||||||
|
logger.debug("Checking if %s is managed by %s ..." % (domain, provider))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Dyndomains will be a list of domains supported by the provider
|
||||||
|
# e.g. [ "nohost.me", "noho.st" ]
|
||||||
|
dyndomains = download_json('https://%s/domains' % provider, timeout=30)
|
||||||
|
except MoulinetteError as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
raise MoulinetteError(errno.EIO,
|
||||||
|
m18n.n('dyndns_could_not_check_provide',
|
||||||
|
domain=domain, provider=provider))
|
||||||
|
|
||||||
|
# Extract 'dyndomain' from 'domain', e.g. 'nohost.me' from 'foo.nohost.me'
|
||||||
|
dyndomain = '.'.join(domain.split('.')[1:])
|
||||||
|
|
||||||
|
return dyndomain in dyndomains
|
||||||
|
|
||||||
|
|
||||||
|
def _dyndns_available(provider, domain):
|
||||||
|
"""
|
||||||
|
Checks if a domain is available from a given provider.
|
||||||
|
|
||||||
|
Keyword arguments:
|
||||||
|
provider -- The url of the provider, e.g. "dyndns.yunohost.org"
|
||||||
|
domain -- The full domain that you'd like.. e.g. "foo.nohost.me"
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if the domain is avaible, False otherwise.
|
||||||
|
"""
|
||||||
|
logger.debug("Checking if domain %s is available on %s ..."
|
||||||
|
% (domain, provider))
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = download_json('https://%s/test/%s' % (provider, domain),
|
||||||
|
expected_status_code=None)
|
||||||
|
except MoulinetteError as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
raise MoulinetteError(errno.EIO,
|
||||||
|
m18n.n('dyndns_could_not_check_available',
|
||||||
|
domain=domain, provider=provider))
|
||||||
|
|
||||||
|
return r == u"Domain %s is available" % domain
|
||||||
|
|
||||||
|
|
||||||
def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None):
|
def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None):
|
||||||
"""
|
"""
|
||||||
Subscribe to a DynDNS service
|
Subscribe to a DynDNS service
|
||||||
|
@ -88,12 +125,16 @@ def dyndns_subscribe(subscribe_host="dyndns.yunohost.org", domain=None, key=None
|
||||||
if domain is None:
|
if domain is None:
|
||||||
domain = _get_maindomain()
|
domain = _get_maindomain()
|
||||||
|
|
||||||
|
# Verify if domain is provided by subscribe_host
|
||||||
|
if not _dyndns_provides(subscribe_host, domain):
|
||||||
|
raise MoulinetteError(errno.ENOENT,
|
||||||
|
m18n.n('dyndns_domain_not_provided',
|
||||||
|
domain=domain, provider=subscribe_host))
|
||||||
|
|
||||||
# Verify if domain is available
|
# Verify if domain is available
|
||||||
try:
|
if not _dyndns_available(subscribe_host, domain):
|
||||||
if requests.get('https://%s/test/%s' % (subscribe_host, domain), timeout=30).status_code != 200:
|
raise MoulinetteError(errno.ENOENT,
|
||||||
raise MoulinetteError(errno.EEXIST, m18n.n('dyndns_unavailable'))
|
m18n.n('dyndns_unavailable', domain=domain))
|
||||||
except requests.ConnectionError:
|
|
||||||
raise MoulinetteError(errno.ENETUNREACH, m18n.n('no_internet_connection'))
|
|
||||||
|
|
||||||
if key is None:
|
if key is None:
|
||||||
if len(glob.glob('/etc/yunohost/dyndns/*.key')) == 0:
|
if len(glob.glob('/etc/yunohost/dyndns/*.key')) == 0:
|
||||||
|
@ -141,75 +182,40 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None,
|
||||||
ipv6 -- IPv6 address to send
|
ipv6 -- IPv6 address to send
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# IPv4
|
# Get old ipv4/v6
|
||||||
|
|
||||||
|
old_ipv4, old_ipv6 = (None, None) # (default values)
|
||||||
|
|
||||||
|
if os.path.isfile(OLD_IPV4_FILE):
|
||||||
|
old_ipv4 = read_file(OLD_IPV4_FILE).rstrip()
|
||||||
|
|
||||||
|
if os.path.isfile(OLD_IPV6_FILE):
|
||||||
|
old_ipv6 = read_file(OLD_IPV6_FILE).rstrip()
|
||||||
|
|
||||||
|
# Get current IPv4 and IPv6
|
||||||
|
(ipv4_, ipv6_) = get_public_ips()
|
||||||
|
|
||||||
if ipv4 is None:
|
if ipv4 is None:
|
||||||
ipv4 = get_public_ip()
|
ipv4 = ipv4_
|
||||||
|
|
||||||
try:
|
|
||||||
with open('/etc/yunohost/dyndns/old_ip', 'r') as f:
|
|
||||||
old_ip = f.readline().rstrip()
|
|
||||||
except IOError:
|
|
||||||
old_ip = '0.0.0.0'
|
|
||||||
|
|
||||||
# IPv6
|
|
||||||
if ipv6 is None:
|
if ipv6 is None:
|
||||||
try:
|
ipv6 = ipv6_
|
||||||
ip_route_out = subprocess.check_output(
|
|
||||||
['ip', 'route', 'get', '2000::']).split('\n')
|
|
||||||
|
|
||||||
if len(ip_route_out) > 0:
|
logger.debug("Old IPv4/v6 are (%s, %s)" % (old_ipv4, old_ipv6))
|
||||||
route = IPRouteLine(ip_route_out[0])
|
logger.debug("Requested IPv4/v6 are (%s, %s)" % (ipv4, ipv6))
|
||||||
if not route.unreachable:
|
|
||||||
ipv6 = route.src_addr
|
|
||||||
|
|
||||||
except (OSError, ValueError) as e:
|
|
||||||
# Unlikely case "ip route" does not return status 0
|
|
||||||
# or produces unexpected output
|
|
||||||
raise MoulinetteError(errno.EBADMSG,
|
|
||||||
"ip route cmd error : {}".format(e))
|
|
||||||
|
|
||||||
if ipv6 is None:
|
|
||||||
logger.info(m18n.n('no_ipv6_connectivity'))
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open('/etc/yunohost/dyndns/old_ipv6', 'r') as f:
|
|
||||||
old_ipv6 = f.readline().rstrip()
|
|
||||||
except IOError:
|
|
||||||
old_ipv6 = '0000:0000:0000:0000:0000:0000:0000:0000'
|
|
||||||
|
|
||||||
# no need to update
|
# no need to update
|
||||||
if old_ip == ipv4 and old_ipv6 == ipv6:
|
if old_ipv4 == ipv4 and old_ipv6 == ipv6:
|
||||||
|
logger.info("No updated needed.")
|
||||||
return
|
return
|
||||||
|
else:
|
||||||
|
logger.info("Updated needed, going on...")
|
||||||
|
|
||||||
|
# If domain is not given, try to guess it from keys available...
|
||||||
if domain is None:
|
if domain is None:
|
||||||
# Retrieve the first registered domain
|
(domain, key) = _guess_current_dyndns_domain(dyn_host)
|
||||||
for path in glob.iglob('/etc/yunohost/dyndns/K*.private'):
|
# If key is not given, pick the first file we find with the domain given
|
||||||
match = RE_DYNDNS_PRIVATE_KEY_SHA512.match(path)
|
elif key is None:
|
||||||
if not match:
|
|
||||||
match = RE_DYNDNS_PRIVATE_KEY_MD5.match(path)
|
|
||||||
if not match:
|
|
||||||
continue
|
|
||||||
_domain = match.group('domain')
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Check if domain is registered
|
|
||||||
request_url = 'https://{0}/test/{1}'.format(dyn_host, _domain)
|
|
||||||
if requests.get(request_url, timeout=30).status_code == 200:
|
|
||||||
continue
|
|
||||||
except requests.ConnectionError:
|
|
||||||
raise MoulinetteError(errno.ENETUNREACH,
|
|
||||||
m18n.n('no_internet_connection'))
|
|
||||||
except requests.exceptions.Timeout:
|
|
||||||
logger.warning("Correction timed out on {}, skip it".format(
|
|
||||||
request_url))
|
|
||||||
domain = _domain
|
|
||||||
key = path
|
|
||||||
break
|
|
||||||
if not domain:
|
|
||||||
raise MoulinetteError(errno.EINVAL,
|
|
||||||
m18n.n('dyndns_no_domain_registered'))
|
|
||||||
|
|
||||||
if key is None:
|
|
||||||
keys = glob.glob('/etc/yunohost/dyndns/K{0}.+*.private'.format(domain))
|
keys = glob.glob('/etc/yunohost/dyndns/K{0}.+*.private'.format(domain))
|
||||||
|
|
||||||
if not keys:
|
if not keys:
|
||||||
|
@ -221,9 +227,12 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None,
|
||||||
if "+157" in key:
|
if "+157" in key:
|
||||||
key = _migrate_from_md5_tsig_to_sha512_tsig(key, domain, dyn_host)
|
key = _migrate_from_md5_tsig_to_sha512_tsig(key, domain, dyn_host)
|
||||||
|
|
||||||
|
# Extract 'host', e.g. 'nohost.me' from 'foo.nohost.me'
|
||||||
host = domain.split('.')[1:]
|
host = domain.split('.')[1:]
|
||||||
host = '.'.join(host)
|
host = '.'.join(host)
|
||||||
|
|
||||||
|
logger.debug("Building zone update file ...")
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
'server %s' % dyn_host,
|
'server %s' % dyn_host,
|
||||||
'zone %s' % host,
|
'zone %s' % host,
|
||||||
|
@ -260,21 +269,27 @@ def dyndns_update(dyn_host="dyndns.yunohost.org", domain=None, key=None,
|
||||||
'send'
|
'send'
|
||||||
]
|
]
|
||||||
|
|
||||||
with open('/etc/yunohost/dyndns/zone', 'w') as zone:
|
# Write the actions to do to update to a file, to be able to pass it
|
||||||
zone.write('\n'.join(lines))
|
# to nsupdate as argument
|
||||||
|
write_to_file(DYNDNS_ZONE, '\n'.join(lines))
|
||||||
|
|
||||||
if os.system('/usr/bin/nsupdate -k %s /etc/yunohost/dyndns/zone' % key) != 0:
|
logger.info("Now pushing new conf to DynDNS host...")
|
||||||
os.system('rm -f /etc/yunohost/dyndns/old_ip')
|
|
||||||
os.system('rm -f /etc/yunohost/dyndns/old_ipv6')
|
try:
|
||||||
|
command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE]
|
||||||
|
subprocess.check_call(command)
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
rm(OLD_IPV4_FILE, force=True) # Remove file (ignore if non-existent)
|
||||||
|
rm(OLD_IPV6_FILE, force=True) # Remove file (ignore if non-existent)
|
||||||
raise MoulinetteError(errno.EPERM,
|
raise MoulinetteError(errno.EPERM,
|
||||||
m18n.n('dyndns_ip_update_failed'))
|
m18n.n('dyndns_ip_update_failed'))
|
||||||
|
|
||||||
logger.success(m18n.n('dyndns_ip_updated'))
|
logger.success(m18n.n('dyndns_ip_updated'))
|
||||||
with open('/etc/yunohost/dyndns/old_ip', 'w') as f:
|
|
||||||
f.write(ipv4)
|
if ipv4 is not None:
|
||||||
|
write_to_file(OLD_IPV4_FILE, ipv4)
|
||||||
if ipv6 is not None:
|
if ipv6 is not None:
|
||||||
with open('/etc/yunohost/dyndns/old_ipv6', 'w') as f:
|
write_to_file(OLD_IPV6_FILE, ipv6)
|
||||||
f.write(ipv6)
|
|
||||||
|
|
||||||
|
|
||||||
def _migrate_from_md5_tsig_to_sha512_tsig(private_key_path, domain, dyn_host):
|
def _migrate_from_md5_tsig_to_sha512_tsig(private_key_path, domain, dyn_host):
|
||||||
|
@ -356,3 +371,32 @@ def dyndns_removecron():
|
||||||
raise MoulinetteError(errno.EIO, m18n.n('dyndns_cron_remove_failed'))
|
raise MoulinetteError(errno.EIO, m18n.n('dyndns_cron_remove_failed'))
|
||||||
|
|
||||||
logger.success(m18n.n('dyndns_cron_removed'))
|
logger.success(m18n.n('dyndns_cron_removed'))
|
||||||
|
|
||||||
|
|
||||||
|
def _guess_current_dyndns_domain(dyn_host):
|
||||||
|
"""
|
||||||
|
This function tries to guess which domain should be updated by
|
||||||
|
"dyndns_update()" because there's not proper management of the current
|
||||||
|
dyndns domain :/ (and at the moment the code doesn't support having several
|
||||||
|
dyndns domain, which is sort of a feature so that people don't abuse the
|
||||||
|
dynette...)
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Retrieve the first registered domain
|
||||||
|
for path in glob.iglob('/etc/yunohost/dyndns/K*.private'):
|
||||||
|
match = RE_DYNDNS_PRIVATE_KEY_MD5.match(path)
|
||||||
|
if not match:
|
||||||
|
match = RE_DYNDNS_PRIVATE_KEY_SHA512.match(path)
|
||||||
|
if not match:
|
||||||
|
continue
|
||||||
|
_domain = match.group('domain')
|
||||||
|
|
||||||
|
# Verify if domain is registered (i.e., if it's available, skip
|
||||||
|
# current domain beause that's not the one we want to update..)
|
||||||
|
if _dyndns_available(dyn_host, _domain):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return (_domain, path)
|
||||||
|
|
||||||
|
raise MoulinetteError(errno.EINVAL,
|
||||||
|
m18n.n('dyndns_no_domain_registered'))
|
||||||
|
|
|
@ -39,10 +39,10 @@ from moulinette.utils import log, filesystem
|
||||||
|
|
||||||
from yunohost.hook import hook_callback
|
from yunohost.hook import hook_callback
|
||||||
|
|
||||||
|
|
||||||
BASE_CONF_PATH = '/home/yunohost.conf'
|
BASE_CONF_PATH = '/home/yunohost.conf'
|
||||||
BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup')
|
BACKUP_CONF_DIR = os.path.join(BASE_CONF_PATH, 'backup')
|
||||||
PENDING_CONF_DIR = os.path.join(BASE_CONF_PATH, 'pending')
|
PENDING_CONF_DIR = os.path.join(BASE_CONF_PATH, 'pending')
|
||||||
|
MOULINETTE_LOCK = "/var/run/moulinette_yunohost.lock"
|
||||||
|
|
||||||
logger = log.getActionLogger('yunohost.service')
|
logger = log.getActionLogger('yunohost.service')
|
||||||
|
|
||||||
|
@ -493,7 +493,8 @@ def _run_service_command(action, service):
|
||||||
service -- Service name
|
service -- Service name
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if service not in _get_services().keys():
|
services = _get_services()
|
||||||
|
if service not in services.keys():
|
||||||
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=service))
|
raise MoulinetteError(errno.EINVAL, m18n.n('service_unknown', service=service))
|
||||||
|
|
||||||
cmd = None
|
cmd = None
|
||||||
|
@ -505,8 +506,23 @@ def _run_service_command(action, service):
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown action '%s'" % action)
|
raise ValueError("Unknown action '%s'" % action)
|
||||||
|
|
||||||
|
need_lock = (services[service].get('need_lock') or False) \
|
||||||
|
and action in ['start', 'stop', 'restart', 'reload']
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ret = subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
|
# Launch the command
|
||||||
|
logger.debug("Running '%s'" % cmd)
|
||||||
|
p = subprocess.Popen(cmd.split(), stderr=subprocess.STDOUT)
|
||||||
|
# If this command needs a lock (because the service uses yunohost
|
||||||
|
# commands inside), find the PID and add a lock for it
|
||||||
|
if need_lock:
|
||||||
|
PID = _give_lock(action, service, p)
|
||||||
|
# Wait for the command to complete
|
||||||
|
p.communicate()
|
||||||
|
# Remove the lock if one was given
|
||||||
|
if need_lock and PID != 0:
|
||||||
|
_remove_lock(PID)
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
# TODO: Log output?
|
# TODO: Log output?
|
||||||
logger.warning(m18n.n('service_cmd_exec_failed', command=' '.join(e.cmd)))
|
logger.warning(m18n.n('service_cmd_exec_failed', command=' '.join(e.cmd)))
|
||||||
|
@ -514,6 +530,41 @@ def _run_service_command(action, service):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _give_lock(action, service, p):
|
||||||
|
|
||||||
|
# Depending of the action, systemctl calls the PID differently :/
|
||||||
|
if action == "start" or action == "restart":
|
||||||
|
systemctl_PID_name = "MainPID"
|
||||||
|
else:
|
||||||
|
systemctl_PID_name = "ControlPID"
|
||||||
|
|
||||||
|
cmd_get_son_PID ="systemctl show %s -p %s" % (service, systemctl_PID_name)
|
||||||
|
son_PID = 0
|
||||||
|
# As long as we did not found the PID and that the command is still running
|
||||||
|
while son_PID == 0 and p.poll() == None:
|
||||||
|
# Call systemctl to get the PID
|
||||||
|
# Output of the command is e.g. ControlPID=1234
|
||||||
|
son_PID = subprocess.check_output(cmd_get_son_PID.split()) \
|
||||||
|
.strip().split("=")[1]
|
||||||
|
son_PID = int(son_PID)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# If we found a PID
|
||||||
|
if son_PID != 0:
|
||||||
|
# Append the PID to the lock file
|
||||||
|
logger.debug("Giving a lock to PID %s for service %s !"
|
||||||
|
% (str(son_PID), service))
|
||||||
|
filesystem.append_to_file(MOULINETTE_LOCK, "\n%s" % str(son_PID))
|
||||||
|
|
||||||
|
return son_PID
|
||||||
|
|
||||||
|
def _remove_lock(PID_to_remove):
|
||||||
|
|
||||||
|
PIDs = filesystem.read_file(MOULINETTE_LOCK).split("\n")
|
||||||
|
PIDs_to_keep = [ PID for PID in PIDs if int(PID) != PID_to_remove ]
|
||||||
|
filesystem.write_to_file(MOULINETTE_LOCK, '\n'.join(PIDs_to_keep))
|
||||||
|
|
||||||
|
|
||||||
def _get_services():
|
def _get_services():
|
||||||
"""
|
"""
|
||||||
Get a dict of managed services with their parameters
|
Get a dict of managed services with their parameters
|
||||||
|
|
|
@ -45,7 +45,7 @@ from moulinette.utils.log import getActionLogger
|
||||||
from moulinette.utils.filesystem import read_json, write_to_json
|
from moulinette.utils.filesystem import read_json, write_to_json
|
||||||
from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron
|
from yunohost.app import app_fetchlist, app_info, app_upgrade, app_ssowatconf, app_list, _install_appslist_fetch_cron
|
||||||
from yunohost.domain import domain_add, domain_list, get_public_ip, _get_maindomain, _set_maindomain
|
from yunohost.domain import domain_add, domain_list, get_public_ip, _get_maindomain, _set_maindomain
|
||||||
from yunohost.dyndns import dyndns_subscribe
|
from yunohost.dyndns import _dyndns_available, _dyndns_provides
|
||||||
from yunohost.firewall import firewall_upnp
|
from yunohost.firewall import firewall_upnp
|
||||||
from yunohost.service import service_status, service_regen_conf, service_log
|
from yunohost.service import service_status, service_regen_conf, service_log
|
||||||
from yunohost.monitor import monitor_disk, monitor_system
|
from yunohost.monitor import monitor_disk, monitor_system
|
||||||
|
@ -253,29 +253,41 @@ def tools_postinstall(domain, password, ignore_dyndns=False):
|
||||||
password -- YunoHost admin password
|
password -- YunoHost admin password
|
||||||
|
|
||||||
"""
|
"""
|
||||||
dyndns = not ignore_dyndns
|
dyndns_provider = "dyndns.yunohost.org"
|
||||||
|
|
||||||
# Do some checks at first
|
# Do some checks at first
|
||||||
if os.path.isfile('/etc/yunohost/installed'):
|
if os.path.isfile('/etc/yunohost/installed'):
|
||||||
raise MoulinetteError(errno.EPERM,
|
raise MoulinetteError(errno.EPERM,
|
||||||
m18n.n('yunohost_already_installed'))
|
m18n.n('yunohost_already_installed'))
|
||||||
if len(domain.split('.')) >= 3 and not ignore_dyndns:
|
|
||||||
try:
|
|
||||||
r = requests.get('https://dyndns.yunohost.org/domains')
|
|
||||||
except requests.ConnectionError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
dyndomains = json.loads(r.text)
|
|
||||||
dyndomain = '.'.join(domain.split('.')[1:])
|
|
||||||
|
|
||||||
if dyndomain in dyndomains:
|
if not ignore_dyndns:
|
||||||
if requests.get('https://dyndns.yunohost.org/test/%s' % domain).status_code == 200:
|
# Check if yunohost dyndns can handle the given domain
|
||||||
dyndns = True
|
# (i.e. is it a .nohost.me ? a .noho.st ?)
|
||||||
else:
|
try:
|
||||||
raise MoulinetteError(errno.EEXIST,
|
is_nohostme_or_nohost = _dyndns_provides(dyndns_provider, domain)
|
||||||
m18n.n('dyndns_unavailable'))
|
# If an exception is thrown, most likely we don't have internet
|
||||||
else:
|
# connectivity or something. Assume that this domain isn't manageable
|
||||||
|
# and inform the user that we could not contact the dyndns host server.
|
||||||
|
except:
|
||||||
|
logger.warning(m18n.n('dyndns_provider_unreachable',
|
||||||
|
provider=dyndns_provider))
|
||||||
|
is_nohostme_or_nohost = False
|
||||||
|
|
||||||
|
# If this is a nohost.me/noho.st, actually check for availability
|
||||||
|
if is_nohostme_or_nohost:
|
||||||
|
# (Except if the user explicitly said he/she doesn't care about dyndns)
|
||||||
|
if ignore_dyndns:
|
||||||
dyndns = False
|
dyndns = False
|
||||||
|
# Check if the domain is available...
|
||||||
|
elif _dyndns_available(dyndns_provider, domain):
|
||||||
|
dyndns = True
|
||||||
|
# If not, abort the postinstall
|
||||||
|
else:
|
||||||
|
raise MoulinetteError(errno.EEXIST,
|
||||||
|
m18n.n('dyndns_unavailable',
|
||||||
|
domain=domain))
|
||||||
|
else:
|
||||||
|
dyndns = False
|
||||||
else:
|
else:
|
||||||
dyndns = False
|
dyndns = False
|
||||||
|
|
||||||
|
|
8
src/yunohost/vendor/acme_tiny/acme_tiny.py
vendored
8
src/yunohost/vendor/acme_tiny/acme_tiny.py
vendored
|
@ -39,7 +39,7 @@ def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA):
|
||||||
raise IOError("OpenSSL Error: {0}".format(err))
|
raise IOError("OpenSSL Error: {0}".format(err))
|
||||||
pub_hex, pub_exp = re.search(
|
pub_hex, pub_exp = re.search(
|
||||||
r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)",
|
r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)",
|
||||||
out.decode('utf8'), re.MULTILINE | re.DOTALL).groups()
|
out.decode('utf8'), re.MULTILINE|re.DOTALL).groups()
|
||||||
pub_exp = "{0:x}".format(int(pub_exp))
|
pub_exp = "{0:x}".format(int(pub_exp))
|
||||||
pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp
|
pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp
|
||||||
header = {
|
header = {
|
||||||
|
@ -82,10 +82,10 @@ def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA):
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
raise IOError("Error loading {0}: {1}".format(csr, err))
|
raise IOError("Error loading {0}: {1}".format(csr, err))
|
||||||
domains = set([])
|
domains = set([])
|
||||||
common_name = re.search(r"Subject:.*? CN=([^\s,;/]+)", out.decode('utf8'))
|
common_name = re.search(r"Subject:.*? CN\s?=\s?([^\s,;/]+)", out.decode('utf8'))
|
||||||
if common_name is not None:
|
if common_name is not None:
|
||||||
domains.add(common_name.group(1))
|
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)
|
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:
|
if subject_alt_names is not None:
|
||||||
for san in subject_alt_names.group(1).split(", "):
|
for san in subject_alt_names.group(1).split(", "):
|
||||||
if san.startswith("DNS:"):
|
if san.startswith("DNS:"):
|
||||||
|
@ -95,7 +95,7 @@ def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA):
|
||||||
log.info("Registering account...")
|
log.info("Registering account...")
|
||||||
code, result = _send_signed_request(CA + "/acme/new-reg", {
|
code, result = _send_signed_request(CA + "/acme/new-reg", {
|
||||||
"resource": "new-reg",
|
"resource": "new-reg",
|
||||||
"agreement": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf",
|
"agreement": json.loads(urlopen(CA + "/directory").read().decode('utf8'))['meta']['terms-of-service'],
|
||||||
})
|
})
|
||||||
if code == 201:
|
if code == 201:
|
||||||
log.info("Registered!")
|
log.info("Registered!")
|
||||||
|
|
Loading…
Add table
Reference in a new issue