diff --git a/data/helpers.d/apt b/data/helpers.d/apt index c48cc0add..5589830c3 100644 --- a/data/helpers.d/apt +++ b/data/helpers.d/apt @@ -260,11 +260,11 @@ ynh_install_app_dependencies () { # And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33-0 on debian) if dpkg --list | grep "php7.0" | grep --quiet --invert-match "7.0.33-0+deb9" then - # And sury ain't already installed - if ! grep --line-number --recursive --quiet "sury" /etc/apt/sources.list* + # And sury ain't already in sources.lists + if ! grep --recursive --quiet "^ *deb.*sury" /etc/apt/sources.list* then # Re-add sury - ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(ynh_get_debian_release) main" --key="https://packages.sury.org/php/apt.gpg" --name=extra_php_version + ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(ynh_get_debian_release) main" --key="https://packages.sury.org/php/apt.gpg" --name=extra_php_version --priority=600 # Pin this sury repository to prevent sury of doing shit for package_to_not_upgrade in "php" "php-fpm" "php-mysql" "php-xml" "php-zip" "php-mbstring" "php-ldap" "php-gd" "php-curl" "php-bz2" "php-json" "php-sqlite3" "php-intl" "openssl" "libssl1.1" "libssl-dev" diff --git a/data/helpers.d/nodejs b/data/helpers.d/nodejs index 3ad0c400f..75472c278 100644 --- a/data/helpers.d/nodejs +++ b/data/helpers.d/nodejs @@ -1,5 +1,6 @@ #!/bin/bash +n_version=6.5.1 n_install_dir="/opt/node_n" node_version_path="$n_install_dir/n/versions/node" # N_PREFIX is the directory of n, it needs to be loaded as a environment variable. @@ -16,8 +17,8 @@ ynh_install_n () { ynh_print_info --message="Installation of N - Node.js version management" # Build an app.src for n mkdir --parents "../conf" - echo "SOURCE_URL=https://github.com/tj/n/archive/v4.1.0.tar.gz -SOURCE_SUM=3983fa3f00d4bf85ba8e21f1a590f6e28938093abe0bb950aeea52b1717471fc" > "../conf/n.src" + echo "SOURCE_URL=https://github.com/tj/n/archive/v${n_version}.tar.gz +SOURCE_SUM=5833f15893b9951a9ed59487e87b6c181d96b83a525846255872c4f92f0d25dd" > "../conf/n.src" # Download and extract n ynh_setup_source --dest_dir="$n_install_dir/git" --source_id=n # Install n @@ -125,7 +126,10 @@ ynh_install_nodejs () { test -x /usr/bin/npm && mv /usr/bin/npm /usr/bin/npm_n # If n is not previously setup, install it - if ! test $(n --version > /dev/null 2>&1) + if ! $n_install_dir/bin/n --version > /dev/null 2>&1 + then + ynh_install_n + elif dpkg --compare-versions "$($n_install_dir/bin/n --version)" lt $n_version then ynh_install_n fi diff --git a/data/helpers.d/php b/data/helpers.d/php index b434c7002..4902e3292 100644 --- a/data/helpers.d/php +++ b/data/helpers.d/php @@ -354,7 +354,7 @@ ynh_install_php () { fi # Add an extra repository for those packages - ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(ynh_get_debian_release) main" --key="https://packages.sury.org/php/apt.gpg" --priority=995 --name=extra_php_version + ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(ynh_get_debian_release) main" --key="https://packages.sury.org/php/apt.gpg" --priority=995 --name=extra_php_version --priority=600 # Install requested dependencies from this extra repository. # Install php-fpm first, otherwise php will install apache as a dependency. diff --git a/data/helpers.d/postgresql b/data/helpers.d/postgresql index cce1f7959..284c02d3e 100644 --- a/data/helpers.d/postgresql +++ b/data/helpers.d/postgresql @@ -213,7 +213,7 @@ ynh_psql_drop_user() { # usage: ynh_psql_setup_db --db_user=user --db_name=name [--db_pwd=pwd] # | arg: -u, --db_user= - Owner of the database # | arg: -n, --db_name= - Name of the database -# | arg: -p, --db_pwd= - Password of the database. If not given, a password will be generated +# | arg: -p, --db_pwd= - Password of the database. If not provided, a password will be generated # # After executing this helper, the password of the created database will be available in $db_pwd # It will also be stored as "psqlpwd" into the app settings. @@ -231,7 +231,7 @@ ynh_psql_setup_db() { if ! ynh_psql_user_exists --user=$db_user; then local new_db_pwd=$(ynh_string_random) # Generate a random password - # If $db_pwd is not given, use new_db_pwd instead for db_pwd + # If $db_pwd is not provided, use new_db_pwd instead for db_pwd db_pwd="${db_pwd:-$new_db_pwd}" ynh_psql_create_user "$db_user" "$db_pwd" diff --git a/data/helpers.d/utils b/data/helpers.d/utils index 9c2f40618..8647002e0 100644 --- a/data/helpers.d/utils +++ b/data/helpers.d/utils @@ -144,8 +144,13 @@ ynh_setup_source () { then # Use the local source file if it is present cp $local_src $src_filename else # If not, download the source + # NB. we have to declare the var as local first, + # otherwise 'local foo=$(false) || echo 'pwet'" does'nt work + # because local always return 0 ... + local out # Timeout option is here to enforce the timeout on dns query and tcp connect (c.f. man wget) - local out=`wget --timeout 900 --no-verbose --output-document=$src_filename $src_url 2>&1` || ynh_print_err --message="$out" + out=$(wget --tries 3 --no-dns-cache --timeout 900 --no-verbose --output-document=$src_filename $src_url 2>&1) \ + || ynh_die --message="$out" fi # Check the control sum diff --git a/data/hooks/conf_regen/01-yunohost b/data/hooks/conf_regen/01-yunohost index abc6f3780..61951fe5f 100755 --- a/data/hooks/conf_regen/01-yunohost +++ b/data/hooks/conf_regen/01-yunohost @@ -67,6 +67,15 @@ EOF # (this make sure that the hash is null / file is flagged as to-delete) mkdir -p $pending_dir/etc/etckeeper touch $pending_dir/etc/etckeeper/etckeeper.conf + + # Skip ntp if inside a container (inspired from the conf of systemd-timesyncd) + mkdir -p ${pending_dir}/etc/systemd/system/ntp.service.d/ + echo " +[Unit] +ConditionCapability=CAP_SYS_TIME +ConditionVirtualization=!container +" > ${pending_dir}/etc/systemd/system/ntp.service.d/ynh-override.conf + } do_post_regen() { @@ -91,6 +100,7 @@ do_post_regen() { [[ ! -e /etc/yunohost/hooks.d ]] || (chown root /etc/yunohost/hooks.d && chmod 700 /etc/yunohost/hooks.d) [[ ! -e /etc/yunohost/apps ]] || (chown root /etc/yunohost/apps && chmod 700 /etc/yunohost/apps) + [[ ! "$regen_conf_files" =~ "ntp.service.d/ynh-override.conf" ]] || { systemctl daemon-reload; systemctl restart ntp; } } _update_services() { diff --git a/data/hooks/conf_regen/06-slapd b/data/hooks/conf_regen/06-slapd index 5fd727a2d..c8fba52fc 100755 --- a/data/hooks/conf_regen/06-slapd +++ b/data/hooks/conf_regen/06-slapd @@ -12,6 +12,8 @@ do_init_regen() { do_pre_regen "" + systemctl daemon-reload + # fix some permissions chown root:openldap /etc/ldap/slapd.conf chown -R openldap:openldap /etc/ldap/schema/ diff --git a/data/hooks/conf_regen/34-mysql b/data/hooks/conf_regen/34-mysql index 74bafbf71..ac2395f34 100755 --- a/data/hooks/conf_regen/34-mysql +++ b/data/hooks/conf_regen/34-mysql @@ -20,9 +20,11 @@ do_post_regen() { # Playing with enable/disable allows to recreate the proper symlinks. if [ ! -e /etc/systemd/system/mysql.service ] then + systemctl stop mysql -q systemctl disable mysql -q systemctl disable mariadb -q systemctl enable mariadb -q + systemctl is-active mariadb -q || systemctl start mariadb fi if [ ! -f /etc/yunohost/mysql ]; then diff --git a/data/other/ffdhe2048.pem b/data/other/ffdhe2048.pem new file mode 100644 index 000000000..9b182b720 --- /dev/null +++ b/data/other/ffdhe2048.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz ++8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a +87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7 +YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi +7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD +ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg== +-----END DH PARAMETERS----- diff --git a/data/templates/nginx/security.conf.inc b/data/templates/nginx/security.conf.inc index dea0f49db..4c6461506 100644 --- a/data/templates/nginx/security.conf.inc +++ b/data/templates/nginx/security.conf.inc @@ -14,9 +14,10 @@ ssl_protocols TLSv1.2; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; -# Uncomment the following directive after DH generation -# > openssl dhparam -out /etc/ssl/private/dh2048.pem -outform PEM -2 2048 -#ssl_dhparam /etc/ssl/private/dh2048.pem; +# Pre-defined FFDHE group (RFC 7919) +# From https://ssl-config.mozilla.org/ffdhe2048.txt +# https://security.stackexchange.com/a/149818 +ssl_dhparam /usr/share/yunohost/other/ffdhe2048.pem; # Follows the Web Security Directives from the Mozilla Dev Lab and the Mozilla Obervatory + Partners # https://wiki.mozilla.org/Security/Guidelines/Web_Security diff --git a/debian/changelog b/debian/changelog index 2916f1b8f..5abadb3f4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,18 @@ +yunohost (3.8.4.7) stable; urgency=low + + - [fix] Remove some remains of glances (17eec25e) + - [fix] Force external resolution for reverse DNS dig (852cd14c) + - [fix] Make sure mysql is an alias to mariadb (e24191ce, ca89607d) + - [fix] Path for ynh_add_fpm_config template in restore (#1001) + - [fix] Add -o Acquire::Retries=3 to fix some stupid network issues happening sometimes with apt (03432349) + - [fix] ynh_setup_source: Retry wget on non-critical failures to try to avoid tmp dns issues (3d66eaec) + - [fix] ynh_setup_source: Calling ynh_print_err in case of error didn't work, and we probably want a ynh_die here (55036fad) + - [i18n] Translations updated for Catalan, French, Italian, Occitan + + Thanks to all contributors <3 ! (JimboJoe, Leandro N., ppr, Quentí, xaloc33, yalh76) + + -- Alexandre Aubin Thu, 04 Jun 2020 02:28:33 +0200 + yunohost (3.8.4.6) stable; urgency=low - [fix] Bump server_names_hash_bucket_size to 128 to avoid nginx exploding for stupid reasons (b3db4d92) diff --git a/debian/install b/debian/install index a814d1617..777d7973e 100644 --- a/debian/install +++ b/debian/install @@ -8,6 +8,7 @@ data/other/yunoprompt.service /etc/systemd/system/ data/other/password/* /usr/share/yunohost/other/password/ data/other/dpkg-origins/yunohost /etc/dpkg/origins data/other/dnsbl_list.yml /usr/share/yunohost/other/ +data/other/ffdhe2048.pem /usr/share/yunohost/other/ data/other/* /usr/share/yunohost/yunohost-config/moulinette/ data/templates/* /usr/share/yunohost/templates/ data/helpers /usr/share/yunohost/ diff --git a/locales/ca.json b/locales/ca.json index 52f2e7f87..f6802f216 100644 --- a/locales/ca.json +++ b/locales/ca.json @@ -537,7 +537,7 @@ "diagnosis_dns_good_conf": "Els registres DNS han estat correctament configurats pel domini {domain} (categoria {category})", "diagnosis_dns_bad_conf": "Alguns registres DNS són incorrectes o no existeixen pel domini {domain} (categoria {category})", "diagnosis_dns_missing_record": "Segons la configuració DNS recomanada, hauríeu d'afegir un registre DNS amb la següent informació.
Tipus: {type}
Nom: {name}
Valor: {value}", - "diagnosis_dns_discrepancy": "El registre DNS de tipus {type} i nom {name} no concorda amb la configuració recomanada.\nValor actual: {current}\nValor esperat: {value}", + "diagnosis_dns_discrepancy": "La configuració DNS següent sembla que no segueix la configuració recomanada:
Tipus: {type}
Nom: {name}
Valor actual: {current}
Valor esperat: {value}", "diagnosis_services_bad_status": "El servei {service} està {status} :(", "diagnosis_diskusage_verylow": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) només té disponibles {free} ({free_percent}%). Hauríeu de considerar alliberar una mica d'espai!", "diagnosis_diskusage_low": "El lloc d'emmagatzematge {mountpoint} (en l'aparell {device}) només té disponibles {free} ({free_percent}%). Aneu amb compte.", @@ -649,5 +649,7 @@ "diagnosis_domain_expiration_error": "Alguns dominis expiraran EN BREUS!", "diagnosis_domain_expires_in": "{domain} expirarà en {days} dies.", "diagnosis_swap_tip": "Vigileu i tingueu en compte que els servidor està allotjant memòria d'intercanvi en una targeta SD o en l'emmagatzematge SSD, això pot reduir dràsticament l'esperança de vida del dispositiu.", - "restore_already_installed_apps": "No s'han pogut restaurar les següents aplicacions perquè ja estan instal·lades: {apps}" + "restore_already_installed_apps": "No s'han pogut restaurar les següents aplicacions perquè ja estan instal·lades: {apps}", + "app_packaging_format_not_supported": "No es pot instal·lar aquesta aplicació ja que el format del paquet no és compatible amb la versió de YunoHost del sistema. Hauríeu de considerar actualitzar el sistema.", + "diagnosis_dns_try_dyndns_update_force": "La configuració DNS d'aquest domini hauria de ser gestionada automàticament per YunoHost. Si aquest no és el cas, podeu intentar forçar-ne l'actualització utilitzant yunohost dyndns update --force." } diff --git a/locales/en.json b/locales/en.json index 428cbd2ac..abc1c1092 100644 --- a/locales/en.json +++ b/locales/en.json @@ -428,6 +428,7 @@ "migration_description_0012_postgresql_password_to_md5_authentication": "Force PostgreSQL authentication to use MD5 for local connections", "migration_description_0013_futureproof_apps_catalog_system": "Migrate to the new future-proof apps catalog system", "migration_description_0014_remove_app_status_json": "Remove legacy status.json app files", + "migration_description_0015_migrate_to_buster": "Upgrade the system to Debian Buster and YunoHost 4.x", "migration_0003_start": "Starting migration to Stretch. The logs will be available in {logfile}.", "migration_0003_patching_sources_list": "Patching the sources.lists…", "migration_0003_main_upgrade": "Starting main upgrade…", @@ -465,6 +466,19 @@ "migration_0011_update_LDAP_database": "Updating LDAP database…", "migration_0011_update_LDAP_schema": "Updating LDAP schema…", "migration_0011_failed_to_remove_stale_object": "Could not remove stale object {dn}: {error}", + "migration_0015_start" : "Starting migration to Buster", + "migration_0015_patching_sources_list": "Patching the sources.lists…", + "migration_0015_main_upgrade": "Starting main upgrade…", + "migration_0015_still_on_stretch_after_main_upgrade": "Something went wrong during the main upgrade, the system appears to still be on Debian Stretch", + "migration_0015_yunohost_upgrade" : "Starting YunoHost core upgrade…", + "migration_0015_not_stretch" : "The current Debian distribution is not Stretch!", + "migration_0015_not_enough_free_space" : "Free space is pretty low in /var/! You should have at least 1GB free to run this migration.", + "migration_0015_system_not_fully_up_to_date": "Your system is not fully up-to-date. Please perform a regular upgrade before running the migration to Buster.", + "migration_0015_general_warning": "Please note that this migration is a delicate operation. The YunoHost team did its best to review and test it, but the migration might still break parts of the system or its apps.\n\nTherefore, it is recommended to:\n - Perform a backup of any critical data or app. More info on https://yunohost.org/backup;\n - Be patient after launching the migration: Depending on your Internet connection and hardware, it might take up to a few hours for everything to upgrade.", + "migration_0015_problematic_apps_warning": "Please note that the following possibly problematic installed apps were detected. It looks like those were not installed from the YunoHost app catalog, or are not flagged as 'working'. Consequently, it cannot be guaranteed that they will still work after the upgrade: {problematic_apps}", + "migration_0015_modified_files": "Please note that the following files were found to be manually modified and might be overwritten following the upgrade: {manually_modified_files}", + "migration_0015_specific_upgrade": "Starting upgrade of system packages that needs to be upgrade independently…", + "migration_0015_cleaning_up": "Cleaning up cache and packages not useful anymore…", "migrations_already_ran": "Those migrations are already done: {ids}", "migrations_cant_reach_migration_file": "Could not access migrations files at the path '%s'", "migrations_dependencies_not_satisfied": "Run these migrations: '{dependencies_id}', before migration {id}.", diff --git a/locales/fr.json b/locales/fr.json index 38a9ad5a7..bd32d7f08 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -426,7 +426,7 @@ "tools_upgrade_special_packages_completed": "La mise à jour des paquets de YunoHost est finie !\nPressez [Entrée] pour revenir à la ligne de commande", "dpkg_lock_not_available": "Cette commande ne peut pas être exécutée pour le moment car un autre programme semble utiliser le verrou de dpkg (le gestionnaire de package système)", "tools_upgrade_cant_unhold_critical_packages": "Impossible de conserver les paquets critiques…", - "tools_upgrade_special_packages_explanation": "La mise à niveau spéciale se poursuivra en arrière-plan. Veuillez ne pas lancer d'autres actions sur votre serveur pendant les 10 prochaines minutes (selon la vitesse du matériel). Après cela, vous devrez peut-être vous reconnecter à l'administrateur Web. Le journal de mise à niveau sera disponible dans Outils → Journal (dans le webadmin) ou en utilisant la « liste des journaux yunohost » (à partir de la ligne de commande).", + "tools_upgrade_special_packages_explanation": "La mise à niveau spécifique à YunoHost se poursuivra en arrière-plan. Veuillez ne pas lancer d'autres actions sur votre serveur pendant les 10 prochaines minutes (selon la vitesse du matériel). Après cela, vous devrez peut-être vous reconnecter à l'administrateur Web. Le journal de mise à niveau sera disponible dans Outils → Journal (dans le webadmin) ou en utilisant la « liste des journaux yunohost » (à partir de la ligne de commande).", "update_apt_cache_failed": "Impossible de mettre à jour le cache APT (gestionnaire de paquets Debian). Voici un extrait du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "update_apt_cache_warning": "Des erreurs se sont produites lors de la mise à jour du cache APT (gestionnaire de paquets Debian). Voici un extrait des lignes du fichier sources.list qui pourrait vous aider à identifier les lignes problématiques :\n{sourceslist}", "backup_permission": "Permission de sauvegarde pour l’application {app:s}", diff --git a/locales/it.json b/locales/it.json index f254d3407..19884b5bb 100644 --- a/locales/it.json +++ b/locales/it.json @@ -1,7 +1,7 @@ { "app_already_installed": "{app:s} è già installata", "app_extraction_failed": "Impossibile estrarre i file di installazione", - "app_not_installed": "{app:s} non è installata", + "app_not_installed": "Impossibile trovare l'applicazione '{app:s}' nell'elenco delle applicazioni installate: {all_apps}", "app_unknown": "Applicazione sconosciuta", "ask_email": "Indirizzo email", "ask_password": "Password", @@ -28,13 +28,13 @@ "admin_password": "Password dell'amministrazione", "admin_password_change_failed": "Impossibile cambiare la password", "admin_password_changed": "La password dell'amministrazione è stata cambiata", - "app_install_files_invalid": "Non sono validi i file di installazione", + "app_install_files_invalid": "Questi file non possono essere installati", "app_manifest_invalid": "Manifesto dell'applicazione non valido: {error}", "app_not_correctly_installed": "{app:s} sembra di non essere installata correttamente", "app_not_properly_removed": "{app:s} non è stata correttamente rimossa", "action_invalid": "L'azione '{action:s}' non è valida", "app_removed": "{app:s} è stata rimossa", - "app_sources_fetch_failed": "Impossibile riportare i file sorgenti", + "app_sources_fetch_failed": "Impossibile riportare i file sorgenti, l'URL è corretto?", "app_upgrade_failed": "Impossibile aggiornare {app:s}", "app_upgraded": "{app:s} è stata aggiornata", "app_requirements_checking": "Controllo i pacchetti richiesti per {app}…", @@ -176,9 +176,9 @@ "app_change_url_failed_nginx_reload": "Non riesco a riavviare NGINX. Questo è il risultato di 'nginx -t':\n{nginx_errors:s}", "app_change_url_identical_domains": "Il vecchio ed il nuovo dominio/percorso_url sono identici ('{domain:s}{path:s}'), nessuna operazione necessaria.", "app_change_url_no_script": "L'applicazione '{app_name:s}' non supporta ancora la modifica dell'URL. Forse dovresti aggiornare l'applicazione.", - "app_change_url_success": "URL dell'applicazione {app:s} cambiato con successo in {domain:s}{path:s}", - "app_make_default_location_already_used": "Impostazione dell'applicazione '{app}' come predefinita del dominio {domain} non riuscita perchè è già stata impostata per l'altra applicazione '{other_app}'", - "app_location_unavailable": "Questo URL non è disponibile o va in conflitto con la/le applicazione/i già installata/e:\n{apps:s}", + "app_change_url_success": "L'URL dell'applicazione {app:s} è stato cambiato in {domain:s}{path:s}", + "app_make_default_location_already_used": "Impostazione dell'applicazione '{app}' come predefinita del dominio non riuscita perché il dominio {domain} è già in uso per l'altra applicazione '{other_app}'", + "app_location_unavailable": "Questo URL non è più disponibile o va in conflitto con la/le applicazione/i già installata/e:\n{apps:s}", "app_upgrade_app_name": "Aggiornando l'applicazione {app}…", "app_upgrade_some_app_failed": "Impossibile aggiornare alcune applicazioni", "backup_abstract_method": "Questo metodo di backup non è ancora stato implementato", @@ -212,8 +212,8 @@ "certmanager_cert_install_success": "Certificato Let's Encrypt per il dominio {domain:s} installato con successo!", "aborting": "Annullamento.", "admin_password_too_long": "Per favore scegli una password più corta di 127 caratteri", - "app_not_upgraded": "Le seguenti app non sono state aggiornate: {apps}", - "app_start_install": "Installando l'applicazione {app}…", + "app_not_upgraded": "Impossibile aggiornare le applicazioni '{failed_app}' e di conseguenza l'aggiornamento delle seguenti applicazione è stato cancellato: {apps}", + "app_start_install": "Installando l'applicazione '{app}'…", "app_start_remove": "Rimuovendo l'applicazione {app}…", "app_start_backup": "Raccogliendo file da salvare nel backup per {app}…", "app_start_restore": "Ripristinando l'applicazione {app}…", @@ -335,5 +335,9 @@ "migration_0003_not_jessie": "La distribuzione attuale non è Jessie!", "migration_0003_system_not_fully_up_to_date": "Il tuo sistema non è completamente aggiornato. Per favore prima esegui un aggiornamento normale prima di migrare a stretch.", "this_action_broke_dpkg": "Questa azione ha danneggiato dpkg/apt (i gestori di pacchetti del sistema)… Puoi provare a risolvere questo problema connettendoti via SSH ed eseguendo `sudo dpkg --configure -a`.", - "app_action_broke_system": "Questa azione sembra avere rotto questi servizi importanti: {services}" + "app_action_broke_system": "Questa azione sembra avere rotto questi servizi importanti: {services}", + "app_remove_after_failed_install": "Rimozione dell'applicazione a causa del fallimento dell'installazione…", + "app_install_script_failed": "Si è verificato un errore nello script di installazione dell'applicazione", + "app_install_failed": "Impossibile installare {app}:{error}", + "app_full_domain_unavailable": "Spiacente, questa app deve essere installata su un proprio dominio, ma altre applicazioni sono state installate sul dominio '{domain}'. Dovresti invece usare un sotto-dominio dedicato per questa app." } diff --git a/locales/oc.json b/locales/oc.json index 13572a1b1..90be0b561 100644 --- a/locales/oc.json +++ b/locales/oc.json @@ -472,7 +472,7 @@ "migrations_not_pending_cant_skip": "Aquestas migracions son pas en espèra, las podètz pas doncas ignorar : {ids}", "app_action_broke_system": "Aquesta accion sembla aver copat de servicis importants : {services}", "diagnosis_display_tip_web": "Podètz anar a la seccion Diagnostic (dins l’ecran d’acuèlh) per veire los problèmas trobats.", - "diagnosis_ip_no_ipv6": "Lo servidor a pas d’adreça IPv5 activa.", + "diagnosis_ip_no_ipv6": "Lo servidor a pas d’adreça IPv6 activa.", "diagnosis_ip_not_connected_at_all": "Lo servidor sembla pas connectat a Internet ?!", "diagnosis_security_all_good": "Cap de vulnerabilitat de seguretat critica pas trobada.", "diagnosis_description_regenconf": "Configuracion sistèma", @@ -537,7 +537,7 @@ "group_cannot_be_deleted": "Lo grop « {group} » pòt pas èsser suprimit manualament.", "diagnosis_found_warnings": "Trobat {warnings} element(s) que se poirián melhorar per {category}.", "diagnosis_dns_missing_record": "Segon la configuracion DNS recomandada, vos calriá ajustar un enregistrament DNS\ntipe: {type}\nnom: {name}\nvalor: {value}", - "diagnosis_dns_discrepancy": "Segon la configuracion DNS recomandada, la valor per l’enregistrament DNS\ntipe: {type}\nnom: {name}\ndeuriá èsser: {current}\nallòc de: {value}", + "diagnosis_dns_discrepancy": "La configuracion DNS seguenta sembla pas la configuracion recomandada :
Tipe : {type}
Nom : {name}
Valors actualas : {current]
Valor esperada : {value}", "diagnosis_regenconf_manually_modified_debian_details": "Es pas problematic, mas car téner d’agacher...", "diagnosis_ports_could_not_diagnose": "Impossible de diagnosticar se los pòrts son accessibles de l’exterior.", "diagnosis_ports_could_not_diagnose_details": "Error : {error}", diff --git a/src/yunohost/app.py b/src/yunohost/app.py index 0cb420d16..6af892b03 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -1358,7 +1358,7 @@ def app_ssowatconf(): url = _sanitized_absolute_url(perm_info["url"]) perm_info["url"] = url if "visitors" in perm_info["allowed"]: - if url not in unprotected_urls: + if url not in unprotected_urls and url not in skipped_urls: unprotected_urls.append(url) # Legacy stuff : we remove now protected-urls that might have been declared as unprotected earlier... diff --git a/src/yunohost/data_migrations/0015_migrate_to_buster.py b/src/yunohost/data_migrations/0015_migrate_to_buster.py new file mode 100644 index 000000000..b56e8322c --- /dev/null +++ b/src/yunohost/data_migrations/0015_migrate_to_buster.py @@ -0,0 +1,197 @@ + +import glob +import os + +from moulinette import m18n +from yunohost.utils.error import YunohostError +from moulinette.utils.log import getActionLogger +from moulinette.utils.process import check_output, call_async_output +from moulinette.utils.filesystem import read_file + +from yunohost.tools import Migration, tools_update, tools_upgrade +from yunohost.app import unstable_apps +from yunohost.regenconf import manually_modified_files +from yunohost.utils.filesystem import free_space_in_directory +from yunohost.utils.packages import get_ynh_package_version, _list_upgradable_apt_packages + +logger = getActionLogger('yunohost.migration') + +class MyMigration(Migration): + + "Upgrade the system to Debian Buster and Yunohost 4.x" + + mode = "manual" + + def run(self): + + self.check_assertions() + + logger.info(m18n.n("migration_0015_start")) + + # + # Patch sources.list + # + logger.info(m18n.n("migration_0015_patching_sources_list")) + self.patch_apt_sources_list() + tools_update(system=True) + + # Tell libc6 it's okay to restart system stuff during the upgrade + os.system("echo 'libc6 libraries/restart-without-asking boolean true' | debconf-set-selections") + + # Don't send an email to root about the postgresql migration. It should be handled automatically after. + os.system("echo 'postgresql-common postgresql-common/obsolete-major seen true' | debconf-set-selections") + + # + # Specific packages upgrades + # + logger.info(m18n.n("migration_0015_specific_upgrade")) + + # Update unscd independently, was 0.53-1+yunohost on stretch (custom build of ours) but now it's 0.53-1+b1 on vanilla buster, + # which for apt appears as a lower version (hence the --allow-downgrades and the hardcoded version number) + unscd_version = check_output('dpkg -s unscd | grep "^Version: " | cut -d " " -f 2') + if "yunohost" in unscd_version: + self.apt_install('unscd=0.53-1+b1 --allow-downgrades') + + # Upgrade libpam-modules independently, small issue related to willing to overwrite a file previously provided by Yunohost + libpammodules_version = check_output('dpkg -s libpam-modules | grep "^Version: " | cut -d " " -f 2') + if not libpammodules_version.startswith("1.3"): + self.apt_install('libpam-modules -o Dpkg::Options::="--force-overwrite"') + + # + # Main upgrade + # + logger.info(m18n.n("migration_0015_main_upgrade")) + + apps_packages = self.get_apps_equivs_packages() + self.hold(apps_packages) + tools_upgrade(system=True, allow_yunohost_upgrade=False) + + if self.debian_major_version() == 9: + raise YunohostError("migration_0015_still_on_stretch_after_main_upgrade") + + # Clean the mess + logger.info(m18n.n("migration_0015_cleaning_up")) + os.system("apt autoremove --assume-yes") + os.system("apt clean --assume-yes") + + # + # Yunohost upgrade + # + logger.info(m18n.n("migration_0015_yunohost_upgrade")) + self.unhold(apps_packages) + tools_upgrade(system=True) + + def debian_major_version(self): + # The python module "platform" and lsb_release are not reliable because + # on some setup, they may still return Release=9 even after upgrading to + # buster ... (Apparently this is related to OVH overriding some stuff + # with /etc/lsb-release for instance -_-) + # Instead, we rely on /etc/os-release which should be the raw info from + # the distribution... + return int(check_output("grep VERSION_ID /etc/os-release | head -n 1 | tr '\"' ' ' | cut -d ' ' -f2")) + + def yunohost_major_version(self): + return int(get_ynh_package_version("yunohost")["version"].split('.')[0]) + + def check_assertions(self): + + # Be on stretch (9.x) and yunohost 3.x + # NB : we do both check to cover situations where the upgrade crashed + # in the middle and debian version could be > 9.x but yunohost package + # would still be in 3.x... + if not self.debian_major_version() == 9 \ + and not self.yunohost_major_version() == 3: + raise YunohostError("migration_0015_not_stretch") + + # Have > 1 Go free space on /var/ ? + if free_space_in_directory("/var/") / (1024**3) < 1.0: + raise YunohostError("migration_0015_not_enough_free_space") + + # Check system is up to date + # (but we don't if 'stretch' is already in the sources.list ... + # which means maybe a previous upgrade crashed and we're re-running it) + if " buster " not in read_file("/etc/apt/sources.list"): + tools_update(system=True) + upgradable_system_packages = list(_list_upgradable_apt_packages()) + if upgradable_system_packages: + raise YunohostError("migration_0015_system_not_fully_up_to_date") + + @property + def disclaimer(self): + + # Avoid having a super long disclaimer + uncessary check if we ain't + # on stretch / yunohost 3.x anymore + # NB : we do both check to cover situations where the upgrade crashed + # in the middle and debian version could be >= 10.x but yunohost package + # would still be in 3.x... + if not self.debian_major_version() == 9 \ + and not self.yunohost_major_version() == 3: + return None + + # Get list of problematic apps ? I.e. not official or community+working + problematic_apps = unstable_apps() + problematic_apps = "".join(["\n - " + app for app in problematic_apps]) + + # Manually modified files ? (c.f. yunohost service regen-conf) + modified_files = manually_modified_files() + modified_files = "".join(["\n - " + f for f in modified_files]) + + message = m18n.n("migration_0015_general_warning") + + if problematic_apps: + message += "\n\n" + m18n.n("migration_0015_problematic_apps_warning", problematic_apps=problematic_apps) + + if modified_files: + message += "\n\n" + m18n.n("migration_0015_modified_files", manually_modified_files=modified_files) + + return message + + def patch_apt_sources_list(self): + + sources_list = glob.glob("/etc/apt/sources.list.d/*.list") + sources_list.append("/etc/apt/sources.list") + + # This : + # - replace single 'stretch' occurence by 'buster' + # - comments lines containing "backports" + # - replace 'stretch/updates' by 'strech/updates' (or same with -) + for f in sources_list: + command = "sed -i -e 's@ stretch @ buster @g' " \ + "-e '/backports/ s@^#*@#@' " \ + "-e 's@ stretch/updates @ buster/updates @g' " \ + "-e 's@ stretch-updates @ buster-updates @g' " \ + "{}".format(f) + os.system(command) + + def get_apps_equivs_packages(self): + + command = "dpkg --get-selections" \ + " | grep -v deinstall" \ + " | awk '{print $1}'" \ + " | { grep 'ynh-deps$' || true; }" + + output = check_output(command).strip() + + return output.split('\n') if output else [] + + def hold(self, packages): + for package in packages: + os.system("apt-mark hold {}".format(package)) + + def unhold(self, packages): + for package in packages: + os.system("apt-mark unhold {}".format(package)) + + def apt_install(self, cmd): + + def is_relevant(l): + return "Reading database ..." not in l.rstrip() + + callbacks = ( + lambda l: logger.info("+ " + l.rstrip() + "\r") if is_relevant(l) else logger.debug(l.rstrip() + "\r"), + lambda l: logger.warning(l.rstrip()), + ) + + cmd = "LC_ALL=C DEBIAN_FRONTEND=noninteractive APT_LISTCHANGES_FRONTEND=none apt install --quiet -o=Dpkg::Use-Pty=0 --fix-broken --assume-yes " + cmd + + call_async_output(cmd, callbacks, shell=True) diff --git a/src/yunohost/regenconf.py b/src/yunohost/regenconf.py index 5bfc5df77..4aad5f921 100644 --- a/src/yunohost/regenconf.py +++ b/src/yunohost/regenconf.py @@ -200,6 +200,42 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run conf_hashes[sshd_config] = _calculate_hash(conf_files[sshd_config]) _update_conf_hashes(category, conf_hashes) + # Consider the following scenario: + # - you add a domain foo.bar + # - the regen-conf creates file /etc/dnsmasq.d/foo.bar + # - the admin manually *deletes* /etc/dnsmasq.d/foo.bar + # - the file is now understood as manually deleted because there's the old file hash in regenconf.yml + # + # ... so far so good, that's the expected behavior. + # + # But then: + # - the admin remove domain foo.bar entirely + # - but now the hash for /etc/dnsmasq.d/foo.bar is *still* in + # regenconf.yml and and the file is still flagged as manually + # modified/deleted... And the user cannot even do anything about it + # except removing the hash in regenconf.yml... + # + # Expected behavior: it should forget about that + # hash because dnsmasq's regen-conf doesn't say anything about what's + # the state of that file so it should assume that it should be deleted. + # + # - then the admin tries to *re-add* foo.bar ! + # - ... but because the file is still flagged as manually modified + # the regen-conf refuses to re-create the file. + # + # Excepted behavior : the regen-conf should have forgot about the hash + # from earlier and this wouldnt happen. + # ------ + # conf_files contain files explicitly set by the current regen conf run + # conf_hashes contain all files known from the past runs + # we compare these to get the list of stale hashes and flag the file as + # "should be removed" + stale_files = set(conf_hashes.keys()) - set(conf_files.keys()) + stale_files_with_non_empty_hash = [f for f in stale_files if conf_hashes.get(f)] + for f in stale_files_with_non_empty_hash: + conf_files[f] = None + # End discussion about stale file hashes + for system_path, pending_path in conf_files.items(): logger.debug("processing pending conf '%s' to system conf '%s'", pending_path, system_path) @@ -211,20 +247,45 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run system_path, pending_path, True) if with_diff else None # Check if the conf must be removed - to_remove = True if os.path.getsize(pending_path) == 0 else False + to_remove = True if pending_path and os.path.getsize(pending_path) == 0 else False # Retrieve and calculate hashes system_hash = _calculate_hash(system_path) saved_hash = conf_hashes.get(system_path, None) new_hash = None if to_remove else _calculate_hash(pending_path) + # -> configuration was previously managed by yunohost but should now + # be removed / unmanaged + if system_path in stale_files_with_non_empty_hash: + # File is already deleted, so let's just silently forget about this hash entirely + if not system_hash: + logger.debug("> forgetting about stale file/hash") + conf_hashes[system_path] = None + conf_status = 'forget-about-it' + regenerated = True + # Otherwise there's still a file on the system but it's not managed by + # Yunohost anymore... But if user requested --force we shall + # force-erase it + elif force: + logger.debug("> force-remove stale file") + regenerated = _regen(system_path) + conf_status = 'force-removed' + # Otherwise, flag the file as manually modified + else: + logger.warning(m18n.n( + 'regenconf_file_manually_modified', + conf=system_path)) + conf_status = 'modified' + # -> system conf does not exists - if not system_hash: + elif not system_hash: if to_remove: logger.debug("> system conf is already removed") os.remove(pending_path) - continue - if not saved_hash or force: + conf_hashes[system_path] = None + conf_status = 'forget-about-it' + regenerated = True + elif not saved_hash or force: if force: logger.debug("> system conf has been manually removed") conf_status = 'force-created' @@ -301,7 +362,7 @@ def regen_conf(operation_logger, names=[], with_diff=False, force=False, dry_run if regenerated: succeed_regen[system_path] = conf_result conf_hashes[system_path] = new_hash - if os.path.isfile(pending_path): + if pending_path and os.path.isfile(pending_path): os.remove(pending_path) else: failed_regen[system_path] = conf_result @@ -417,7 +478,7 @@ def _get_files_diff(orig_file, new_file, as_string=False, skip_header=True): def _calculate_hash(path): """Calculate the MD5 hash of a file""" - if not os.path.exists(path): + if not path or not os.path.exists(path): return None hasher = hashlib.md5() @@ -503,6 +564,12 @@ def _update_conf_hashes(category, hashes): if category_conf is None: category_conf = {} + # If a file shall be removed and is indeed removed, forget entirely about + # that path. + # It avoid keeping weird old entries like + # /etc/nginx/conf.d/some.domain.that.got.removed.conf + hashes = {path: hash_ for path, hash_ in hashes.items() if hash_ is not None or os.path.exists(path)} + category_conf['conffiles'] = hashes categories[category] = category_conf _save_regenconf_infos(categories) diff --git a/src/yunohost/tests/test_regenconf.py b/src/yunohost/tests/test_regenconf.py index f618278f3..cbe26e1bc 100644 --- a/src/yunohost/tests/test_regenconf.py +++ b/src/yunohost/tests/test_regenconf.py @@ -1,20 +1,13 @@ -import glob import os -import pytest -import shutil -import requests -from conftest import message, raiseYunohostError - -from moulinette import m18n -from moulinette.utils.filesystem import mkdir - -from yunohost.domain import _get_maindomain, domain_add, domain_remove, domain_list -from yunohost.utils.error import YunohostError -from yunohost.regenconf import manually_modified_files, regen_conf, _get_conf_hashes, _force_clear_hashes +from conftest import message +from yunohost.domain import domain_add, domain_remove, domain_list +from yunohost.regenconf import regen_conf, manually_modified_files, _get_conf_hashes, _force_clear_hashes TEST_DOMAIN = "secondarydomain.test" -TEST_DOMAIN_NGINX_CONFIG = "/etc/nginx/conf.d/secondarydomain.test.conf" +TEST_DOMAIN_NGINX_CONFIG = "/etc/nginx/conf.d/%s.conf" % TEST_DOMAIN +TEST_DOMAIN_DNSMASQ_CONFIG = "/etc/dnsmasq.d/%s" % TEST_DOMAIN + SSHD_CONFIG = "/etc/ssh/sshd_config" def setup_function(function): @@ -22,11 +15,13 @@ def setup_function(function): _force_clear_hashes([TEST_DOMAIN_NGINX_CONFIG]) clean() + def teardown_function(function): clean() _force_clear_hashes([TEST_DOMAIN_NGINX_CONFIG]) + def clean(): assert os.system("pgrep slapd >/dev/null") == 0 @@ -116,3 +111,94 @@ def test_ssh_conf_unmanaged_and_manually_modified(mocker): assert SSHD_CONFIG in _get_conf_hashes("ssh") assert SSHD_CONFIG not in manually_modified_files() + + +def test_stale_hashes_get_removed_if_empty(): + """ + This is intended to test that if a file gets removed and is indeed removed, + we don't keep a useless empty hash corresponding to an old file. + In this case, we test this using the dnsmasq conf file (we don't do this + using the nginx conf file because it's already force-removed during + domain_remove()) + """ + + domain_add(TEST_DOMAIN) + + assert os.path.exists(TEST_DOMAIN_DNSMASQ_CONFIG) + assert TEST_DOMAIN_DNSMASQ_CONFIG in _get_conf_hashes("dnsmasq") + + domain_remove(TEST_DOMAIN) + + assert not os.path.exists(TEST_DOMAIN_DNSMASQ_CONFIG) + assert TEST_DOMAIN_DNSMASQ_CONFIG not in _get_conf_hashes("dnsmasq") + + +def test_stale_hashes_if_file_manually_deleted(): + """ + Same as other test, but manually delete the file in between and check + behavior + """ + + domain_add(TEST_DOMAIN) + + assert os.path.exists(TEST_DOMAIN_DNSMASQ_CONFIG) + assert TEST_DOMAIN_DNSMASQ_CONFIG in _get_conf_hashes("dnsmasq") + + os.remove(TEST_DOMAIN_DNSMASQ_CONFIG) + + assert not os.path.exists(TEST_DOMAIN_DNSMASQ_CONFIG) + + regen_conf(names=["dnsmasq"]) + + assert not os.path.exists(TEST_DOMAIN_DNSMASQ_CONFIG) + assert TEST_DOMAIN_DNSMASQ_CONFIG in _get_conf_hashes("dnsmasq") + + domain_remove(TEST_DOMAIN) + + assert not os.path.exists(TEST_DOMAIN_DNSMASQ_CONFIG) + assert TEST_DOMAIN_DNSMASQ_CONFIG not in _get_conf_hashes("dnsmasq") + +# This test only works if you comment the part at the end of the regen-conf in +# dnsmasq that auto-flag /etc/dnsmasq.d/foo.bar as "to be removed" (using touch) +# ... But we want to keep it because they also possibly flag files that were +# never known by the regen-conf (e.g. if somebody adds a +# /etc/dnsmasq.d/my.custom.extension) +# Ideally we could use a system that's able to properly state 'no file in this +# folder should exist except the ones excplicitly defined by regen-conf' but +# that's too much work for the scope of this commit. +# +# ... Anyway, the proper way to write these tests would be to use a dummy +# regen-conf hook just for tests but meh I'm lazy +# +#def test_stale_hashes_if_file_manually_modified(): +# """ +# Same as other test, but manually delete the file in between and check +# behavior +# """ +# +# domain_add(TEST_DOMAIN) +# +# assert os.path.exists(TEST_DOMAIN_DNSMASQ_CONFIG) +# assert TEST_DOMAIN_DNSMASQ_CONFIG in _get_conf_hashes("dnsmasq") +# +# os.system("echo '#pwet' > %s" % TEST_DOMAIN_DNSMASQ_CONFIG) +# +# assert os.path.exists(TEST_DOMAIN_DNSMASQ_CONFIG) +# assert open(TEST_DOMAIN_DNSMASQ_CONFIG).read().strip() == "#pwet" +# +# regen_conf(names=["dnsmasq"]) +# +# assert os.path.exists(TEST_DOMAIN_DNSMASQ_CONFIG) +# assert open(TEST_DOMAIN_DNSMASQ_CONFIG).read().strip() == "#pwet" +# assert TEST_DOMAIN_DNSMASQ_CONFIG in _get_conf_hashes("dnsmasq") +# +# domain_remove(TEST_DOMAIN) +# +# assert os.path.exists(TEST_DOMAIN_DNSMASQ_CONFIG) +# assert open(TEST_DOMAIN_DNSMASQ_CONFIG).read().strip() == "#pwet" +# assert TEST_DOMAIN_DNSMASQ_CONFIG in _get_conf_hashes("dnsmasq") +# +# regen_conf(names=["dnsmasq"], force=True) +# +# assert not os.path.exists(TEST_DOMAIN_DNSMASQ_CONFIG) +# assert TEST_DOMAIN_DNSMASQ_CONFIG not in _get_conf_hashes("dnsmasq") \ No newline at end of file diff --git a/src/yunohost/tools.py b/src/yunohost/tools.py index 9221e6c7d..7cdcc8641 100644 --- a/src/yunohost/tools.py +++ b/src/yunohost/tools.py @@ -489,7 +489,7 @@ def _list_upgradable_apps(): @is_unit_operation() -def tools_upgrade(operation_logger, apps=None, system=False): +def tools_upgrade(operation_logger, apps=None, system=False, allow_yunohost_upgrade=True): """ Update apps & package cache, then display changelog @@ -552,7 +552,7 @@ def tools_upgrade(operation_logger, apps=None, system=False): # Critical packages are packages that we can't just upgrade # randomly from yunohost itself... upgrading them is likely to - critical_packages = ("moulinette", "yunohost", "yunohost-admin", "ssowat", "python") + critical_packages = ["moulinette", "yunohost", "yunohost-admin", "ssowat"] critical_packages_upgradable = [p["name"] for p in upgradables if p["name"] in critical_packages] noncritical_packages_upgradable = [p["name"] for p in upgradables if p["name"] not in critical_packages] @@ -605,7 +605,7 @@ def tools_upgrade(operation_logger, apps=None, system=False): # # Critical packages upgrade # - if critical_packages_upgradable: + if critical_packages_upgradable and allow_yunohost_upgrade: logger.info(m18n.n("tools_upgrade_special_packages"))