mirror of
https://github.com/YunoHost/yunohost.git
synced 2024-09-03 20:06:10 +02:00
Merge branch 'bullseye' into manifestv2
This commit is contained in:
commit
0f9560ffe0
43 changed files with 543 additions and 328 deletions
24
.codeclimate.yml
Normal file
24
.codeclimate.yml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
version: "2"
|
||||||
|
plugins:
|
||||||
|
duplication:
|
||||||
|
enabled: true
|
||||||
|
config:
|
||||||
|
languages:
|
||||||
|
python:
|
||||||
|
python_version: 3
|
||||||
|
shellcheck:
|
||||||
|
enabled: true
|
||||||
|
pep8:
|
||||||
|
enabled: true
|
||||||
|
fixme:
|
||||||
|
enabled: true
|
||||||
|
sonar-python:
|
||||||
|
enabled: true
|
||||||
|
config:
|
||||||
|
tests_patterns:
|
||||||
|
- bin/*
|
||||||
|
- data/**
|
||||||
|
- doc/*
|
||||||
|
- src/**
|
||||||
|
- tests/**
|
|
@ -2,7 +2,7 @@
|
||||||
stages:
|
stages:
|
||||||
- build
|
- build
|
||||||
- install
|
- install
|
||||||
- tests
|
- test
|
||||||
- lint
|
- lint
|
||||||
- doc
|
- doc
|
||||||
- translation
|
- translation
|
||||||
|
@ -13,6 +13,17 @@ default:
|
||||||
# All jobs are interruptible by default
|
# All jobs are interruptible by default
|
||||||
interruptible: true
|
interruptible: true
|
||||||
|
|
||||||
|
code_quality:
|
||||||
|
tags:
|
||||||
|
- docker
|
||||||
|
|
||||||
|
code_quality_html:
|
||||||
|
extends: code_quality
|
||||||
|
variables:
|
||||||
|
REPORT_FORMAT: html
|
||||||
|
artifacts:
|
||||||
|
paths: [gl-code-quality-report.html]
|
||||||
|
|
||||||
# see: https://docs.gitlab.com/ee/ci/yaml/#switch-between-branch-pipelines-and-merge-request-pipelines
|
# see: https://docs.gitlab.com/ee/ci/yaml/#switch-between-branch-pipelines-and-merge-request-pipelines
|
||||||
workflow:
|
workflow:
|
||||||
rules:
|
rules:
|
||||||
|
@ -29,4 +40,5 @@ variables:
|
||||||
YNH_BUILD_DIR: "ynh-build"
|
YNH_BUILD_DIR: "ynh-build"
|
||||||
|
|
||||||
include:
|
include:
|
||||||
|
- template: Code-Quality.gitlab-ci.yml
|
||||||
- local: .gitlab/ci/*.gitlab-ci.yml
|
- local: .gitlab/ci/*.gitlab-ci.yml
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
- pip3 install -U mock pip pytest pytest-cov pytest-mock pytest-sugar requests-mock tox ansi2html black jinja2
|
- pip3 install -U mock pip pytest pytest-cov pytest-mock pytest-sugar requests-mock tox ansi2html black jinja2
|
||||||
|
|
||||||
.test-stage:
|
.test-stage:
|
||||||
stage: tests
|
stage: test
|
||||||
image: "after-install"
|
image: "after-install"
|
||||||
variables:
|
variables:
|
||||||
PYTEST_ADDOPTS: "--color=yes"
|
PYTEST_ADDOPTS: "--color=yes"
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
########################################
|
########################################
|
||||||
|
|
||||||
full-tests:
|
full-tests:
|
||||||
stage: tests
|
stage: test
|
||||||
image: "before-install"
|
image: "before-install"
|
||||||
variables:
|
variables:
|
||||||
PYTEST_ADDOPTS: "--color=yes"
|
PYTEST_ADDOPTS: "--color=yes"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
test-i18n-keys:
|
test-i18n-keys:
|
||||||
stage: translation
|
stage: translation
|
||||||
script:
|
script:
|
||||||
- python3 maintenance/missing_i18n_keys --check
|
- python3 maintenance/missing_i18n_keys.py --check
|
||||||
only:
|
only:
|
||||||
changes:
|
changes:
|
||||||
- locales/en.json
|
- locales/en.json
|
||||||
|
@ -23,8 +23,8 @@ autofix-translated-strings:
|
||||||
script:
|
script:
|
||||||
# create a local branch that will overwrite distant one
|
# create a local branch that will overwrite distant one
|
||||||
- git checkout -b "ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}" --no-track
|
- git checkout -b "ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}" --no-track
|
||||||
- python3 maintenance/missing_i18n_keys --fix
|
- python3 maintenance/missing_i18n_keys.py --fix
|
||||||
- python3 maintenanceautofix_locale_format.py
|
- python3 maintenance/autofix_locale_format.py
|
||||||
- '[ $(git diff -w | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit
|
- '[ $(git diff -w | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit
|
||||||
- git commit -am "[CI] Reformat / remove stale translated strings" || true
|
- git commit -am "[CI] Reformat / remove stale translated strings" || true
|
||||||
- git push -f origin "HEAD":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}"
|
- git push -f origin "HEAD":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}"
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
# by YunoHost
|
# by YunoHost
|
||||||
|
|
||||||
Protocol 2
|
Protocol 2
|
||||||
|
# PLEASE: if you wish to change the ssh port properly in YunoHost, use this command:
|
||||||
|
# yunohost settings set security.ssh.port -v <port>
|
||||||
Port {{ port }}
|
Port {{ port }}
|
||||||
|
|
||||||
{% if ipv6_enabled == "true" %}ListenAddress ::{% endif %}
|
{% if ipv6_enabled == "true" %}ListenAddress ::{% endif %}
|
||||||
|
@ -53,9 +55,13 @@ PermitEmptyPasswords no
|
||||||
ChallengeResponseAuthentication no
|
ChallengeResponseAuthentication no
|
||||||
UsePAM yes
|
UsePAM yes
|
||||||
|
|
||||||
# Change to no to disable tunnelled clear text passwords
|
# PLEASE: if you wish to force everybody to authenticate using ssh keys, run this command:
|
||||||
# (i.e. everybody will need to authenticate using ssh keys)
|
# yunohost settings set security.ssh.password_authentication -v no
|
||||||
|
{% if password_authentication == "False" %}
|
||||||
|
PasswordAuthentication no
|
||||||
|
{% else %}
|
||||||
#PasswordAuthentication yes
|
#PasswordAuthentication yes
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
# Post-login stuff
|
# Post-login stuff
|
||||||
Banner /etc/issue.net
|
Banner /etc/issue.net
|
||||||
|
|
4
debian/control
vendored
4
debian/control
vendored
|
@ -14,11 +14,11 @@ Depends: ${python3:Depends}, ${misc:Depends}
|
||||||
, python3-psutil, python3-requests, python3-dnspython, python3-openssl
|
, python3-psutil, python3-requests, python3-dnspython, python3-openssl
|
||||||
, python3-miniupnpc, python3-dbus, python3-jinja2
|
, python3-miniupnpc, python3-dbus, python3-jinja2
|
||||||
, python3-toml, python3-packaging, python3-publicsuffix2
|
, python3-toml, python3-packaging, python3-publicsuffix2
|
||||||
, python3-ldap, python3-zeroconf, python3-lexicon,
|
, python3-ldap, python3-zeroconf (>= 0.36), python3-lexicon,
|
||||||
, python-is-python3
|
, python-is-python3
|
||||||
, nginx, nginx-extras (>=1.18)
|
, nginx, nginx-extras (>=1.18)
|
||||||
, apt, apt-transport-https, apt-utils, dirmngr
|
, apt, apt-transport-https, apt-utils, dirmngr
|
||||||
, openssh-server, iptables, fail2ban, dnsutils, bind9utils
|
, openssh-server, iptables, fail2ban, bind9-dnsutils
|
||||||
, openssl, ca-certificates, netcat-openbsd, iproute2
|
, openssl, ca-certificates, netcat-openbsd, iproute2
|
||||||
, slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd
|
, slapd, ldap-utils, sudo-ldap, libnss-ldapd, unscd, libpam-ldapd
|
||||||
, dnsmasq, resolvconf, libnss-myhostname
|
, dnsmasq, resolvconf, libnss-myhostname
|
||||||
|
|
|
@ -393,6 +393,12 @@ ynh_install_extra_app_dependencies() {
|
||||||
# Install requested dependencies from this extra repository.
|
# Install requested dependencies from this extra repository.
|
||||||
ynh_install_app_dependencies "$package"
|
ynh_install_app_dependencies "$package"
|
||||||
|
|
||||||
|
# Force to upgrade to the last version...
|
||||||
|
# Without doing apt install, an already installed dep is not upgraded
|
||||||
|
local apps_auto_installed="$(apt-mark showauto $package)"
|
||||||
|
ynh_package_install "$package"
|
||||||
|
apt-mark auto $apps_auto_installed
|
||||||
|
|
||||||
# Remove this extra repository after packages are installed
|
# Remove this extra repository after packages are installed
|
||||||
ynh_remove_extra_repo --name=$app
|
ynh_remove_extra_repo --name=$app
|
||||||
}
|
}
|
||||||
|
|
|
@ -935,4 +935,11 @@ _ynh_apply_default_permissions() {
|
||||||
chown $app:$app $target
|
chown $app:$app $target
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Crons should be owned by root otherwise they probably don't run
|
||||||
|
if echo "$target" | grep -q '^/etc/cron'
|
||||||
|
then
|
||||||
|
chmod 400 $target
|
||||||
|
chown root:root $target
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@ EOF
|
||||||
# If we subscribed to a dyndns domain, add the corresponding cron
|
# If we subscribed to a dyndns domain, add the corresponding cron
|
||||||
# - delay between 0 and 60 secs to spread the check over a 1 min window
|
# - delay between 0 and 60 secs to spread the check over a 1 min window
|
||||||
# - do not run the command if some process already has the lock, to avoid queuing hundreds of commands...
|
# - do not run the command if some process already has the lock, to avoid queuing hundreds of commands...
|
||||||
if ls -l /etc/yunohost/dyndns/K*.private 2>/dev/null; then
|
if ls -l /etc/yunohost/dyndns/K*.key 2>/dev/null; then
|
||||||
cat >$pending_dir/etc/cron.d/yunohost-dyndns <<EOF
|
cat >$pending_dir/etc/cron.d/yunohost-dyndns <<EOF
|
||||||
SHELL=/bin/bash
|
SHELL=/bin/bash
|
||||||
# Every 10 minutes,
|
# Every 10 minutes,
|
||||||
|
|
|
@ -22,6 +22,7 @@ do_pre_regen() {
|
||||||
# Support different strategy for security configurations
|
# Support different strategy for security configurations
|
||||||
export compatibility="$(yunohost settings get 'security.ssh.compatibility')"
|
export compatibility="$(yunohost settings get 'security.ssh.compatibility')"
|
||||||
export port="$(yunohost settings get 'security.ssh.port')"
|
export port="$(yunohost settings get 'security.ssh.port')"
|
||||||
|
export password_authentication="$(yunohost settings get 'security.ssh.password_authentication')"
|
||||||
export ssh_keys
|
export ssh_keys
|
||||||
export ipv6_enabled
|
export ipv6_enabled
|
||||||
ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config"
|
ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config"
|
||||||
|
|
|
@ -12,7 +12,12 @@ do_pre_regen() {
|
||||||
echo "deb https://packages.sury.org/php/ $(lsb_release --codename --short) main" > "${pending_dir}/etc/apt/sources.list.d/extra_php_version.list"
|
echo "deb https://packages.sury.org/php/ $(lsb_release --codename --short) main" > "${pending_dir}/etc/apt/sources.list.d/extra_php_version.list"
|
||||||
|
|
||||||
# Ban some packages from sury
|
# Ban some packages from sury
|
||||||
packages_to_refuse_from_sury="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"
|
echo "
|
||||||
|
Package: php-common
|
||||||
|
Pin: origin \"packages.sury.org\"
|
||||||
|
Pin-Priority: 500" >>"${pending_dir}/etc/apt/preferences.d/extra_php_version"
|
||||||
|
|
||||||
|
packages_to_refuse_from_sury="php php-* openssl libssl1.1 libssl-dev"
|
||||||
for package in $packages_to_refuse_from_sury; do
|
for package in $packages_to_refuse_from_sury; do
|
||||||
echo "
|
echo "
|
||||||
Package: $package
|
Package: $package
|
||||||
|
@ -20,7 +25,6 @@ Pin: origin \"packages.sury.org\"
|
||||||
Pin-Priority: -1" >>"${pending_dir}/etc/apt/preferences.d/extra_php_version"
|
Pin-Priority: -1" >>"${pending_dir}/etc/apt/preferences.d/extra_php_version"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Ban some packages that users may inadvertendly try to install such as apache2 ...
|
|
||||||
echo "
|
echo "
|
||||||
|
|
||||||
# PLEASE READ THIS WARNING AND DON'T EDIT THIS FILE
|
# PLEASE READ THIS WARNING AND DON'T EDIT THIS FILE
|
||||||
|
|
|
@ -42,7 +42,6 @@ do_pre_regen() {
|
||||||
chown postfix ${pending_dir}/etc/postfix/sasl_passwd
|
chown postfix ${pending_dir}/etc/postfix/sasl_passwd
|
||||||
|
|
||||||
cat <<<"[${relay_host}]:${relay_port} ${relay_user}:${relay_password}" >${postfix_dir}/sasl_passwd
|
cat <<<"[${relay_host}]:${relay_port} ${relay_user}:${relay_password}" >${postfix_dir}/sasl_passwd
|
||||||
postmap ${postfix_dir}/sasl_passwd
|
|
||||||
fi
|
fi
|
||||||
export main_domain
|
export main_domain
|
||||||
export domain_list="$YNH_DOMAINS"
|
export domain_list="$YNH_DOMAINS"
|
||||||
|
@ -71,6 +70,7 @@ do_post_regen() {
|
||||||
if [ -e /etc/postfix/sasl_passwd ]; then
|
if [ -e /etc/postfix/sasl_passwd ]; then
|
||||||
chmod 750 /etc/postfix/sasl_passwd*
|
chmod 750 /etc/postfix/sasl_passwd*
|
||||||
chown postfix:root /etc/postfix/sasl_passwd*
|
chown postfix:root /etc/postfix/sasl_passwd*
|
||||||
|
postmap /etc/postfix/sasl_passwd
|
||||||
fi
|
fi
|
||||||
|
|
||||||
[[ -z "$regen_conf_files" ]] \
|
[[ -z "$regen_conf_files" ]] \
|
||||||
|
|
|
@ -285,7 +285,7 @@
|
||||||
"diagnosis_no_cache": "Kein Diagnose Cache aktuell für die Kategorie '{category}'",
|
"diagnosis_no_cache": "Kein Diagnose Cache aktuell für die Kategorie '{category}'",
|
||||||
"diagnosis_ip_no_ipv4": "Der Server hat kein funktionierendes IPv4.",
|
"diagnosis_ip_no_ipv4": "Der Server hat kein funktionierendes IPv4.",
|
||||||
"diagnosis_ip_connected_ipv6": "Der Server ist mit dem Internet über IPv6 verbunden!",
|
"diagnosis_ip_connected_ipv6": "Der Server ist mit dem Internet über IPv6 verbunden!",
|
||||||
"diagnosis_ip_no_ipv6": "Der Server hat kein funktionierendes IPv6.",
|
"diagnosis_ip_no_ipv6": "Der Server verfügt nicht über eine funktionierende IPv6-Adresse.",
|
||||||
"diagnosis_ip_not_connected_at_all": "Der Server scheint überhaupt nicht mit dem Internet verbunden zu sein!?",
|
"diagnosis_ip_not_connected_at_all": "Der Server scheint überhaupt nicht mit dem Internet verbunden zu sein!?",
|
||||||
"diagnosis_failed_for_category": "Diagnose fehlgeschlagen für die Kategorie '{category}': {error}",
|
"diagnosis_failed_for_category": "Diagnose fehlgeschlagen für die Kategorie '{category}': {error}",
|
||||||
"diagnosis_cache_still_valid": "(Cache noch gültig für {category} Diagnose. Es wird keine neue Diagnose durchgeführt!)",
|
"diagnosis_cache_still_valid": "(Cache noch gültig für {category} Diagnose. Es wird keine neue Diagnose durchgeführt!)",
|
||||||
|
@ -315,10 +315,10 @@
|
||||||
"diagnosis_domain_not_found_details": "Die Domäne {domain} existiert nicht in der WHOIS-Datenbank oder sie ist abgelaufen!",
|
"diagnosis_domain_not_found_details": "Die Domäne {domain} existiert nicht in der WHOIS-Datenbank oder sie ist abgelaufen!",
|
||||||
"diagnosis_domain_expiration_not_found": "Das Ablaufdatum einiger Domains kann nicht überprüft werden",
|
"diagnosis_domain_expiration_not_found": "Das Ablaufdatum einiger Domains kann nicht überprüft werden",
|
||||||
"diagnosis_dns_try_dyndns_update_force": "Die DNS-Konfiguration dieser Domäne sollte automatisch von YunoHost verwaltet werden. Andernfalls könntest Du mittels <cmd>yunohost dyndns update --force</cmd> ein Update erzwingen.",
|
"diagnosis_dns_try_dyndns_update_force": "Die DNS-Konfiguration dieser Domäne sollte automatisch von YunoHost verwaltet werden. Andernfalls könntest Du mittels <cmd>yunohost dyndns update --force</cmd> ein Update erzwingen.",
|
||||||
"diagnosis_dns_point_to_doc": "Bitte schaue in die Dokumentation unter <a href='https://yunohost.org/dns_config'>https://yunohost.org/dns_config</a> wenn du hilfe bei der Konfiguration der DNS Einträge brauchst.",
|
"diagnosis_dns_point_to_doc": "Bitte schauen Sie in der Dokumentation unter <a href='https://yunohost.org/dns_config'>https://yunohost.org/dns_config</a> nach, wenn Sie Hilfe bei der Konfiguration der DNS-Einträge brauchen.",
|
||||||
"diagnosis_dns_discrepancy": "Der folgende DNS Eintrag scheint nicht den empfohlenen Einstellungen zu entsprechen:<br>Typ: <code>{type}</code><br>Name: <code>{name}</code><br>Aktueller Wert: <code>{current}</code><br>Erwarteter Wert: <code>{value}</code>",
|
"diagnosis_dns_discrepancy": "Der folgende DNS Eintrag scheint nicht den empfohlenen Einstellungen zu entsprechen:<br>Typ: <code>{type}</code><br>Name: <code>{name}</code><br>Aktueller Wert: <code>{current}</code><br>Erwarteter Wert: <code>{value}</code>",
|
||||||
"diagnosis_dns_missing_record": "Gemäß der empfohlenen DNS-Konfiguration sollten Sie einen DNS-Eintrag mit den folgenden Informationen hinzufügen.<br>Typ: <code>{type}</code><br>Name: <code>{name}</code><br>Wert: <code>{value}</code>",
|
"diagnosis_dns_missing_record": "Gemäß der empfohlenen DNS-Konfiguration sollten Sie einen DNS-Eintrag mit den folgenden Informationen hinzufügen.<br>Typ: <code>{type}</code><br>Name: <code>{name}</code><br>Wert: <code>{value}</code>",
|
||||||
"diagnosis_dns_bad_conf": "Einige DNS Einträge für die Domäne {domain} fehlen oder sind nicht korrekt (Kategorie {category})",
|
"diagnosis_dns_bad_conf": "Einige DNS-Einträge für die Domäne {domain} fehlen oder sind nicht korrekt (Kategorie {category})",
|
||||||
"diagnosis_ip_local": "Lokale IP: <code>{local}</code>",
|
"diagnosis_ip_local": "Lokale IP: <code>{local}</code>",
|
||||||
"diagnosis_ip_global": "Globale IP: <code>{global}</code>",
|
"diagnosis_ip_global": "Globale IP: <code>{global}</code>",
|
||||||
"diagnosis_ip_no_ipv6_tip": "Die Verwendung von IPv6 ist nicht Voraussetzung für das Funktionieren Ihres Servers, trägt aber zur Gesundheit des Internet als Ganzes bei. IPv6 sollte normalerweise automatisch von Ihrem Server oder Ihrem Provider konfiguriert werden, sofern verfügbar. Andernfalls müßen Sie einige Dinge manuell konfigurieren. Weitere Informationen finden Sie hier: <a href='https://yunohost.org/#/ipv6'>https://yunohost.org/#/ipv6</a>. Wenn Sie IPv6 nicht aktivieren können oder Ihnen das zu technisch ist, können Sie diese Warnung gefahrlos ignorieren.",
|
"diagnosis_ip_no_ipv6_tip": "Die Verwendung von IPv6 ist nicht Voraussetzung für das Funktionieren Ihres Servers, trägt aber zur Gesundheit des Internet als Ganzes bei. IPv6 sollte normalerweise automatisch von Ihrem Server oder Ihrem Provider konfiguriert werden, sofern verfügbar. Andernfalls müßen Sie einige Dinge manuell konfigurieren. Weitere Informationen finden Sie hier: <a href='https://yunohost.org/#/ipv6'>https://yunohost.org/#/ipv6</a>. Wenn Sie IPv6 nicht aktivieren können oder Ihnen das zu technisch ist, können Sie diese Warnung gefahrlos ignorieren.",
|
||||||
|
@ -560,7 +560,7 @@
|
||||||
"service_description_dovecot": "Ermöglicht es E-Mail-Clients auf Konten zuzugreifen (IMAP und POP3)",
|
"service_description_dovecot": "Ermöglicht es E-Mail-Clients auf Konten zuzugreifen (IMAP und POP3)",
|
||||||
"service_description_dnsmasq": "Verarbeitet die Auflösung des Domainnamens (DNS)",
|
"service_description_dnsmasq": "Verarbeitet die Auflösung des Domainnamens (DNS)",
|
||||||
"restore_backup_too_old": "Dieses Backup kann nicht wieder hergestellt werden, weil es von einer zu alten YunoHost Version stammt.",
|
"restore_backup_too_old": "Dieses Backup kann nicht wieder hergestellt werden, weil es von einer zu alten YunoHost Version stammt.",
|
||||||
"service_description_slapd": "Speichert Benutzer:innen, Domains und verbundene Informationen",
|
"service_description_slapd": "Speichert Benutzer:innen, Domänen und verbundene Informationen",
|
||||||
"service_description_rspamd": "Spamfilter und andere E-Mail-Merkmale",
|
"service_description_rspamd": "Spamfilter und andere E-Mail-Merkmale",
|
||||||
"service_description_redis-server": "Eine spezialisierte Datenbank für den schnellen Datenzugriff, die Aufgabenwarteschlange und die Kommunikation zwischen Programmen",
|
"service_description_redis-server": "Eine spezialisierte Datenbank für den schnellen Datenzugriff, die Aufgabenwarteschlange und die Kommunikation zwischen Programmen",
|
||||||
"service_description_postfix": "Wird benutzt, um E-Mails zu senden und zu empfangen",
|
"service_description_postfix": "Wird benutzt, um E-Mails zu senden und zu empfangen",
|
||||||
|
|
|
@ -382,6 +382,7 @@
|
||||||
"global_settings_setting_security_password_user_strength": "User password strength",
|
"global_settings_setting_security_password_user_strength": "User password strength",
|
||||||
"global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)",
|
"global_settings_setting_security_postfix_compatibility": "Compatibility vs. security tradeoff for the Postfix server. Affects the ciphers (and other security-related aspects)",
|
||||||
"global_settings_setting_security_ssh_compatibility": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)",
|
"global_settings_setting_security_ssh_compatibility": "Compatibility vs. security tradeoff for the SSH server. Affects the ciphers (and other security-related aspects)",
|
||||||
|
"global_settings_setting_security_ssh_password_authentication": "Allow password authentication for SSH",
|
||||||
"global_settings_setting_security_ssh_port": "SSH port",
|
"global_settings_setting_security_ssh_port": "SSH port",
|
||||||
"global_settings_setting_security_webadmin_allowlist": "IP adresses allowed to access the webadmin. Comma-separated.",
|
"global_settings_setting_security_webadmin_allowlist": "IP adresses allowed to access the webadmin. Comma-separated.",
|
||||||
"global_settings_setting_security_webadmin_allowlist_enabled": "Allow only some IPs to access the webadmin.",
|
"global_settings_setting_security_webadmin_allowlist_enabled": "Allow only some IPs to access the webadmin.",
|
||||||
|
|
|
@ -599,5 +599,9 @@
|
||||||
"diagnosis_apps_bad_quality": "Esta aplicación está etiquetada como defectuosa en el catálogo de aplicaciones YunoHost. Podría ser un problema temporal mientras las personas responsables corrigen el asunto. Mientras tanto, la actualización de esta aplicación está desactivada.",
|
"diagnosis_apps_bad_quality": "Esta aplicación está etiquetada como defectuosa en el catálogo de aplicaciones YunoHost. Podría ser un problema temporal mientras las personas responsables corrigen el asunto. Mientras tanto, la actualización de esta aplicación está desactivada.",
|
||||||
"diagnosis_apps_broken": "Esta aplicación está etiquetada como defectuosa en el catálogo de aplicaciones YunoHost. Podría ser un problema temporal mientras las personas responsables corrigen el asunto. Mientras tanto, la actualización de esta aplicación está desactivada.",
|
"diagnosis_apps_broken": "Esta aplicación está etiquetada como defectuosa en el catálogo de aplicaciones YunoHost. Podría ser un problema temporal mientras las personas responsables corrigen el asunto. Mientras tanto, la actualización de esta aplicación está desactivada.",
|
||||||
"diagnosis_apps_deprecated_practices": "La versión instalada de esta aplicación usa aún prácticas de empaquetado obsoletas. Deberías actualizarla.",
|
"diagnosis_apps_deprecated_practices": "La versión instalada de esta aplicación usa aún prácticas de empaquetado obsoletas. Deberías actualizarla.",
|
||||||
"diagnosis_apps_outdated_ynh_requirement": "La versión instalada de esta aplicación solo necesita YunoHost >= 2.x, lo que indica que no está al día con la buena praxis de ayudas y empaquetado recomendadas. Deberías actualizarla."
|
"diagnosis_apps_outdated_ynh_requirement": "La versión instalada de esta aplicación solo necesita YunoHost >= 2.x, lo que indica que no está al día con la buena praxis de ayudas y empaquetado recomendadas. Deberías actualizarla.",
|
||||||
|
"domain_dns_conf_special_use_tld": "Este dominio se basa en un dominio de primer nivel (TLD) de usos especiales como .local o .test y no debería tener entradas DNS reales.",
|
||||||
|
"diagnosis_sshd_config_insecure": "Parece que la configuración SSH ha sido modificada manualmente, y es insegura porque no tiene ninguna instrucción 'AllowGroups' o 'AllowUsers' para limitar el acceso a los usuarios autorizados.",
|
||||||
|
"domain_dns_push_not_applicable": "La configuración automática de los registros DNS no puede realizarse en el dominio {domain}. Deberìas configurar manualmente los registros DNS siguiendo la <a href='https://yunohost.org/dns'>documentación</a>.",
|
||||||
|
"domain_dns_push_managed_in_parent_domain": "La configuración automática de los registros DNS es administrada desde el dominio superior {parent_domain}."
|
||||||
}
|
}
|
|
@ -480,7 +480,7 @@
|
||||||
"diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit dans <a href='https://yunohost.org/isp_box_config'>https://yunohost.org/isp_box_config</a>",
|
"diagnosis_ports_forwarding_tip": "Pour résoudre ce problème, vous devez probablement configurer la redirection de port sur votre routeur Internet comme décrit dans <a href='https://yunohost.org/isp_box_config'>https://yunohost.org/isp_box_config</a>",
|
||||||
"diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.",
|
"diagnosis_http_connection_error": "Erreur de connexion : impossible de se connecter au domaine demandé, il est probablement injoignable.",
|
||||||
"diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie '{category}'",
|
"diagnosis_no_cache": "Pas encore de cache de diagnostique pour la catégorie '{category}'",
|
||||||
"yunohost_postinstall_end_tip": "La post-installation terminée ! Pour finaliser votre configuration, il est recommandé de :\n- ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l'interface web (ou 'yunohost user create <nom d'utilisateur>' en ligne de commande) ;\n- diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou 'yunohost diagnosis run' en ligne de commande) ;\n- lire les parties 'Finalisation de votre configuration' et 'Découverte de YunoHost' dans le guide de l'administrateur : https://yunohost.org/admindoc.",
|
"yunohost_postinstall_end_tip": "La post-installation est terminée ! Pour finaliser votre configuration, il est recommandé de :\n- ajouter un premier utilisateur depuis la section \"Utilisateurs\" de l'interface web (ou 'yunohost user create <nom d'utilisateur>' en ligne de commande) ;\n- diagnostiquer les potentiels problèmes dans la section \"Diagnostic\" de l'interface web (ou 'yunohost diagnosis run' en ligne de commande) ;\n- lire les parties 'Finalisation de votre configuration' et 'Découverte de YunoHost' dans le guide de l'administrateur : https://yunohost.org/admindoc.",
|
||||||
"diagnosis_services_bad_status_tip": "Vous pouvez essayer de <a href='#/services/{service}'>redémarrer le service</a>, et si cela ne fonctionne pas, consultez <a href='#/services/{service}'>les journaux de service dans le webadmin</a> (à partir de la ligne de commande, vous pouvez le faire avec <cmd>yunohost service restart {service}</cmd> et <cmd>yunohost service log {service}</cmd> ).",
|
"diagnosis_services_bad_status_tip": "Vous pouvez essayer de <a href='#/services/{service}'>redémarrer le service</a>, et si cela ne fonctionne pas, consultez <a href='#/services/{service}'>les journaux de service dans le webadmin</a> (à partir de la ligne de commande, vous pouvez le faire avec <cmd>yunohost service restart {service}</cmd> et <cmd>yunohost service log {service}</cmd> ).",
|
||||||
"diagnosis_http_bad_status_code": "Le système de diagnostique n'a pas réussi à contacter votre serveur. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration Nginx est à jour et qu'un reverse-proxy n'interfère pas.",
|
"diagnosis_http_bad_status_code": "Le système de diagnostique n'a pas réussi à contacter votre serveur. Il se peut qu'une autre machine réponde à la place de votre serveur. Vérifiez que le port 80 est correctement redirigé, que votre configuration Nginx est à jour et qu'un reverse-proxy n'interfère pas.",
|
||||||
"diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l'extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que Nginx est en cours d'exécution et qu'un pare-feu n'interfère pas.",
|
"diagnosis_http_timeout": "Expiration du délai en essayant de contacter votre serveur de l'extérieur. Il semble être inaccessible. Vérifiez que vous transférez correctement le port 80, que Nginx est en cours d'exécution et qu'un pare-feu n'interfère pas.",
|
||||||
|
@ -580,7 +580,7 @@
|
||||||
"diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement le remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.",
|
"diagnosis_rootfstotalspace_critical": "Le système de fichiers racine ne fait que {space} ! Vous allez certainement le remplir très rapidement ! Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.",
|
||||||
"diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Cela peut suffire, mais faites attention car vous risquez de les remplir rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.",
|
"diagnosis_rootfstotalspace_warning": "Le système de fichiers racine n'est que de {space}. Cela peut suffire, mais faites attention car vous risquez de les remplir rapidement... Il est recommandé d'avoir au moins 16 GB pour ce système de fichiers.",
|
||||||
"app_restore_script_failed": "Une erreur s'est produite dans le script de restauration de l'application",
|
"app_restore_script_failed": "Une erreur s'est produite dans le script de restauration de l'application",
|
||||||
"restore_backup_too_old": "Cette sauvegarde ne peut pas être restaurée car elle provient d'une version trop ancienne de YunoHost.",
|
"restore_backup_too_old": "Cette sauvegarde ne peut pas être restaurée car elle provient d'une version de YunoHost trop ancienne.",
|
||||||
"log_backup_create": "Créer une archive de sauvegarde",
|
"log_backup_create": "Créer une archive de sauvegarde",
|
||||||
"global_settings_setting_ssowat_panel_overlay_enabled": "Activer la superposition de la vignette SSOwat",
|
"global_settings_setting_ssowat_panel_overlay_enabled": "Activer la superposition de la vignette SSOwat",
|
||||||
"migration_ldap_rollback_success": "Système rétabli dans son état initial.",
|
"migration_ldap_rollback_success": "Système rétabli dans son état initial.",
|
||||||
|
@ -684,5 +684,6 @@
|
||||||
"migration_0021_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires...",
|
"migration_0021_cleaning_up": "Nettoyage du cache et des paquets qui ne sont plus nécessaires...",
|
||||||
"migration_0021_patch_yunohost_conflicts": "Application du correctif pour contourner le problème de conflit...",
|
"migration_0021_patch_yunohost_conflicts": "Application du correctif pour contourner le problème de conflit...",
|
||||||
"migration_0021_not_buster": "La distribution Debian actuelle n'est pas Buster !",
|
"migration_0021_not_buster": "La distribution Debian actuelle n'est pas Buster !",
|
||||||
"migration_description_0021_migrate_to_bullseye": "Mise à niveau du système vers Debian Bullseye et YunoHost 11.x"
|
"migration_description_0021_migrate_to_bullseye": "Mise à niveau du système vers Debian Bullseye et YunoHost 11.x",
|
||||||
|
"global_settings_setting_security_ssh_password_authentication": "Autoriser l'authentification par mot de passe pour SSH"
|
||||||
}
|
}
|
|
@ -684,5 +684,6 @@
|
||||||
"migration_0021_patch_yunohost_conflicts": "Solucionando os problemas e conflitos...",
|
"migration_0021_patch_yunohost_conflicts": "Solucionando os problemas e conflitos...",
|
||||||
"migration_description_0021_migrate_to_bullseye": "Actualizar o sistema a Debian Bullseye e YunoHost 11.x",
|
"migration_description_0021_migrate_to_bullseye": "Actualizar o sistema a Debian Bullseye e YunoHost 11.x",
|
||||||
"migration_0021_system_not_fully_up_to_date": "O teu sistema non está completamente actualizado. Fai unha actualización normal antes de executar a migración a Bullseye.",
|
"migration_0021_system_not_fully_up_to_date": "O teu sistema non está completamente actualizado. Fai unha actualización normal antes de executar a migración a Bullseye.",
|
||||||
"migration_0021_general_warning": "Ten en conta que a migración é unha operación delicada. O equipo de YunoHost fixo todo o que puido para revisalo e probalo, pero aínda así poderían acontecer fallos no sistema ou apps.\n\nAsí as cousas, é recomendable:\n - Facer unha copia de apoio dos datos e apps importantes. Máis info en https://yunohost.org/backup;\n - Ter paciencia unha vez inicias a migración: dependendo da túa conexión a internet e hardware, podería levarlle varias horas completar o proceso."
|
"migration_0021_general_warning": "Ten en conta que a migración é unha operación delicada. O equipo de YunoHost fixo todo o que puido para revisalo e probalo, pero aínda así poderían acontecer fallos no sistema ou apps.\n\nAsí as cousas, é recomendable:\n - Facer unha copia de apoio dos datos e apps importantes. Máis info en https://yunohost.org/backup;\n - Ter paciencia unha vez inicias a migración: dependendo da túa conexión a internet e hardware, podería levarlle varias horas completar o proceso.",
|
||||||
|
"global_settings_setting_security_ssh_password_authentication": "Permitir autenticación con contrasinal para SSH"
|
||||||
}
|
}
|
||||||
|
|
|
@ -684,5 +684,6 @@
|
||||||
"migration_0021_not_enough_free_space": "Вільного місця в /var/ досить мало! У вас повинно бути не менше 1 ГБ вільного місця, щоб запустити цю міграцію.",
|
"migration_0021_not_enough_free_space": "Вільного місця в /var/ досить мало! У вас повинно бути не менше 1 ГБ вільного місця, щоб запустити цю міграцію.",
|
||||||
"migration_0021_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Bullseye.",
|
"migration_0021_system_not_fully_up_to_date": "Ваша система не повністю оновлена. Будь ласка, виконайте регулярне оновлення перед запуском міграції на Bullseye.",
|
||||||
"migration_0021_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частину системи або її застосунків.\n\nТому рекомендовано:\n - Виконати резервне копіювання всіх важливих даних або застосунків. Подробиці на сайті https://yunohost.org/backup; \n - Наберіться терпіння після запуску міграції: В залежності від вашого з'єднання з Інтернетом і апаратного забезпечення, оновлення може зайняти до декількох годин.",
|
"migration_0021_general_warning": "Будь ласка, зверніть увагу, що ця міграція є делікатною операцією. Команда YunoHost зробила все можливе, щоб перевірити і протестувати її, але міграція все ще може порушити частину системи або її застосунків.\n\nТому рекомендовано:\n - Виконати резервне копіювання всіх важливих даних або застосунків. Подробиці на сайті https://yunohost.org/backup; \n - Наберіться терпіння після запуску міграції: В залежності від вашого з'єднання з Інтернетом і апаратного забезпечення, оновлення може зайняти до декількох годин.",
|
||||||
"migration_description_0021_migrate_to_bullseye": "Оновлення системи до Debian Bullseye і YunoHost 11.x"
|
"migration_description_0021_migrate_to_bullseye": "Оновлення системи до Debian Bullseye і YunoHost 11.x",
|
||||||
|
"global_settings_setting_security_ssh_password_authentication": "Дозволити автентифікацію паролем для SSH"
|
||||||
}
|
}
|
|
@ -34,7 +34,6 @@
|
||||||
#############################
|
#############################
|
||||||
_global:
|
_global:
|
||||||
namespace: yunohost
|
namespace: yunohost
|
||||||
cookie_name: yunohost.admin
|
|
||||||
authentication:
|
authentication:
|
||||||
api: ldap_admin
|
api: ldap_admin
|
||||||
cli: null
|
cli: null
|
||||||
|
|
32
src/app.py
32
src/app.py
|
@ -177,7 +177,7 @@ def app_info(app, full=False):
|
||||||
ret["label"] = permissions.get(app + ".main", {}).get("label")
|
ret["label"] = permissions.get(app + ".main", {}).get("label")
|
||||||
|
|
||||||
if not ret["label"]:
|
if not ret["label"]:
|
||||||
logger.warning("Failed to get label for app %s ?" % app)
|
logger.warning(f"Failed to get label for app {app} ?")
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -291,8 +291,7 @@ def app_map(app=None, raw=False, user=None):
|
||||||
if user:
|
if user:
|
||||||
if not app_id + ".main" in permissions:
|
if not app_id + ".main" in permissions:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Uhoh, no main permission was found for app %s ... sounds like an app was only partially removed due to another bug :/"
|
f"Uhoh, no main permission was found for app {app_id} ... sounds like an app was only partially removed due to another bug :/"
|
||||||
% app_id
|
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
main_perm = permissions[app_id + ".main"]
|
main_perm = permissions[app_id + ".main"]
|
||||||
|
@ -412,7 +411,7 @@ def app_change_url(operation_logger, app, domain, path):
|
||||||
# Execute App change_url script
|
# Execute App change_url script
|
||||||
ret = hook_exec(change_url_script, env=env_dict)[0]
|
ret = hook_exec(change_url_script, env=env_dict)[0]
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
msg = "Failed to change '%s' url." % app
|
msg = f"Failed to change '{app}' url."
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
operation_logger.error(msg)
|
operation_logger.error(msg)
|
||||||
|
|
||||||
|
@ -651,7 +650,7 @@ def app_upgrade(app=[], url=None, file=None, force=False, no_safety_backup=False
|
||||||
if upgrade_failed or broke_the_system:
|
if upgrade_failed or broke_the_system:
|
||||||
|
|
||||||
# display this if there are remaining apps
|
# display this if there are remaining apps
|
||||||
if apps[number + 1:]:
|
if apps[number + 1 :]:
|
||||||
not_upgraded_apps = apps[number:]
|
not_upgraded_apps = apps[number:]
|
||||||
logger.error(
|
logger.error(
|
||||||
m18n.n(
|
m18n.n(
|
||||||
|
@ -909,7 +908,7 @@ def app_install(
|
||||||
for question in questions:
|
for question in questions:
|
||||||
# Or should it be more generally question.redact ?
|
# Or should it be more generally question.redact ?
|
||||||
if question.type == "password":
|
if question.type == "password":
|
||||||
del env_dict_for_logging["YNH_APP_ARG_%s" % question.name.upper()]
|
del env_dict_for_logging[f"YNH_APP_ARG_{question.name.upper()}"]
|
||||||
|
|
||||||
operation_logger.extra.update({"env": env_dict_for_logging})
|
operation_logger.extra.update({"env": env_dict_for_logging})
|
||||||
|
|
||||||
|
@ -956,8 +955,7 @@ def app_install(
|
||||||
# This option is meant for packagers to debug their apps more easily
|
# This option is meant for packagers to debug their apps more easily
|
||||||
if no_remove_on_failure:
|
if no_remove_on_failure:
|
||||||
raise YunohostError(
|
raise YunohostError(
|
||||||
"The installation of %s failed, but was not cleaned up as requested by --no-remove-on-failure."
|
f"The installation of {app_id} failed, but was not cleaned up as requested by --no-remove-on-failure.",
|
||||||
% app_id,
|
|
||||||
raw_msg=True,
|
raw_msg=True,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
@ -1508,9 +1506,9 @@ def app_action_run(operation_logger, app, action, args=None):
|
||||||
actions = {x["id"]: x for x in actions}
|
actions = {x["id"]: x for x in actions}
|
||||||
|
|
||||||
if action not in actions:
|
if action not in actions:
|
||||||
|
available_actions = ", ".join(actions.keys()),
|
||||||
raise YunohostValidationError(
|
raise YunohostValidationError(
|
||||||
"action '%s' not available for app '%s', available actions are: %s"
|
f"action '{action}' not available for app '{app}', available actions are: {available_actions}",
|
||||||
% (action, app, ", ".join(actions.keys())),
|
|
||||||
raw_msg=True,
|
raw_msg=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1910,8 +1908,7 @@ def _get_manifest_of_app(path):
|
||||||
manifest = read_json(os.path.join(path, "manifest.json"))
|
manifest = read_json(os.path.join(path, "manifest.json"))
|
||||||
else:
|
else:
|
||||||
raise YunohostError(
|
raise YunohostError(
|
||||||
"There doesn't seem to be any manifest file in %s ... It looks like an app was not correctly installed/removed."
|
f"There doesn't seem to be any manifest file in {path} ... It looks like an app was not correctly installed/removed.",
|
||||||
% path,
|
|
||||||
raw_msg=True,
|
raw_msg=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2196,7 +2193,7 @@ def _extract_app_from_gitrepo(
|
||||||
cmd = f"git ls-remote --exit-code {url} {branch} | awk '{{print $1}}'"
|
cmd = f"git ls-remote --exit-code {url} {branch} | awk '{{print $1}}'"
|
||||||
manifest["remote"]["revision"] = check_output(cmd)
|
manifest["remote"]["revision"] = check_output(cmd)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("cannot get last commit hash because: %s ", e)
|
logger.warning(f"cannot get last commit hash because: {e}")
|
||||||
else:
|
else:
|
||||||
manifest["remote"]["revision"] = revision
|
manifest["remote"]["revision"] = revision
|
||||||
manifest["lastUpdate"] = app_info.get("lastUpdate")
|
manifest["lastUpdate"] = app_info.get("lastUpdate")
|
||||||
|
@ -2409,14 +2406,7 @@ def _assert_no_conflicting_apps(domain, path, ignore_app=None, full_domain=False
|
||||||
if conflicts:
|
if conflicts:
|
||||||
apps = []
|
apps = []
|
||||||
for path, app_id, app_label in conflicts:
|
for path, app_id, app_label in conflicts:
|
||||||
apps.append(
|
apps.append(f" * {domain}{path} → {app_label} ({app_id})")
|
||||||
" * {domain:s}{path:s} → {app_label:s} ({app_id:s})".format(
|
|
||||||
domain=domain,
|
|
||||||
path=path,
|
|
||||||
app_id=app_id,
|
|
||||||
app_label=app_label,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if full_domain:
|
if full_domain:
|
||||||
raise YunohostValidationError("app_full_domain_unavailable", domain=domain)
|
raise YunohostValidationError("app_full_domain_unavailable", domain=domain)
|
||||||
|
|
|
@ -103,9 +103,7 @@ def _initialize_apps_catalog_system():
|
||||||
)
|
)
|
||||||
write_to_yaml(APPS_CATALOG_CONF, default_apps_catalog_list)
|
write_to_yaml(APPS_CATALOG_CONF, default_apps_catalog_list)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError(
|
raise YunohostError(f"Could not initialize the apps catalog system... : {e}", raw_msg=True)
|
||||||
"Could not initialize the apps catalog system... : %s" % str(e)
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.success(m18n.n("apps_catalog_init_success"))
|
logger.success(m18n.n("apps_catalog_init_success"))
|
||||||
|
|
||||||
|
@ -121,14 +119,12 @@ def _read_apps_catalog_list():
|
||||||
# by returning [] if list_ is None
|
# by returning [] if list_ is None
|
||||||
return list_ if list_ else []
|
return list_ if list_ else []
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError("Could not read the apps_catalog list ... : %s" % str(e))
|
raise YunohostError(f"Could not read the apps_catalog list ... : {e}", raw_msg=True)
|
||||||
|
|
||||||
|
|
||||||
def _actual_apps_catalog_api_url(base_url):
|
def _actual_apps_catalog_api_url(base_url):
|
||||||
|
|
||||||
return "{base_url}/v{version}/apps.json".format(
|
return f"{base_url}/v{APPS_CATALOG_API_VERSION}/apps.json"
|
||||||
base_url=base_url, version=APPS_CATALOG_API_VERSION
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _update_apps_catalog():
|
def _update_apps_catalog():
|
||||||
|
@ -172,16 +168,11 @@ def _update_apps_catalog():
|
||||||
apps_catalog_content["from_api_version"] = APPS_CATALOG_API_VERSION
|
apps_catalog_content["from_api_version"] = APPS_CATALOG_API_VERSION
|
||||||
|
|
||||||
# Save the apps_catalog data in the cache
|
# Save the apps_catalog data in the cache
|
||||||
cache_file = "{cache_folder}/{list}.json".format(
|
cache_file = f"{APPS_CATALOG_CACHE}/{apps_catalog_id}.json"
|
||||||
cache_folder=APPS_CATALOG_CACHE, list=apps_catalog_id
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
write_to_json(cache_file, apps_catalog_content)
|
write_to_json(cache_file, apps_catalog_content)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError(
|
raise YunohostError(f"Unable to write cache data for {apps_catalog_id} apps_catalog : {e}", raw_msg=True)
|
||||||
"Unable to write cache data for %s apps_catalog : %s"
|
|
||||||
% (apps_catalog_id, str(e))
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.success(m18n.n("apps_catalog_update_success"))
|
logger.success(m18n.n("apps_catalog_update_success"))
|
||||||
|
|
||||||
|
@ -197,9 +188,7 @@ def _load_apps_catalog():
|
||||||
for apps_catalog_id in [L["id"] for L in _read_apps_catalog_list()]:
|
for apps_catalog_id in [L["id"] for L in _read_apps_catalog_list()]:
|
||||||
|
|
||||||
# Let's load the json from cache for this catalog
|
# Let's load the json from cache for this catalog
|
||||||
cache_file = "{cache_folder}/{list}.json".format(
|
cache_file = f"{APPS_CATALOG_CACHE}/{apps_catalog_id}.json"
|
||||||
cache_folder=APPS_CATALOG_CACHE, list=apps_catalog_id
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
apps_catalog_content = (
|
apps_catalog_content = (
|
||||||
|
@ -230,10 +219,8 @@ def _load_apps_catalog():
|
||||||
# (N.B. : there's a small edge case where multiple apps catalog could be listing the same apps ...
|
# (N.B. : there's a small edge case where multiple apps catalog could be listing the same apps ...
|
||||||
# in which case we keep only the first one found)
|
# in which case we keep only the first one found)
|
||||||
if app in merged_catalog["apps"]:
|
if app in merged_catalog["apps"]:
|
||||||
logger.warning(
|
other_catalog = merged_catalog["apps"][app]["repository"]
|
||||||
"Duplicate app %s found between apps catalog %s and %s"
|
logger.warning(f"Duplicate app {app} found between apps catalog {apps_catalog_id} and {other_catalog}")
|
||||||
% (app, apps_catalog_id, merged_catalog["apps"][app]["repository"])
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
info["repository"] = apps_catalog_id
|
info["repository"] = apps_catalog_id
|
||||||
|
|
|
@ -8,10 +8,14 @@ import time
|
||||||
|
|
||||||
from moulinette import m18n
|
from moulinette import m18n
|
||||||
from moulinette.authentication import BaseAuthenticator
|
from moulinette.authentication import BaseAuthenticator
|
||||||
from yunohost.utils.error import YunohostError
|
from moulinette.utils.text import random_ascii
|
||||||
|
|
||||||
|
from yunohost.utils.error import YunohostError, YunohostAuthenticationError
|
||||||
|
|
||||||
logger = logging.getLogger("yunohost.authenticators.ldap_admin")
|
logger = logging.getLogger("yunohost.authenticators.ldap_admin")
|
||||||
|
|
||||||
|
session_secret = random_ascii()
|
||||||
|
|
||||||
|
|
||||||
class Authenticator(BaseAuthenticator):
|
class Authenticator(BaseAuthenticator):
|
||||||
|
|
||||||
|
@ -66,3 +70,59 @@ class Authenticator(BaseAuthenticator):
|
||||||
# Free the connection, we don't really need it to keep it open as the point is only to check authentication...
|
# Free the connection, we don't really need it to keep it open as the point is only to check authentication...
|
||||||
if con:
|
if con:
|
||||||
con.unbind_s()
|
con.unbind_s()
|
||||||
|
|
||||||
|
def set_session_cookie(self, infos):
|
||||||
|
|
||||||
|
from bottle import response
|
||||||
|
|
||||||
|
assert isinstance(infos, dict)
|
||||||
|
|
||||||
|
# This allows to generate a new session id or keep the existing one
|
||||||
|
current_infos = self.get_session_cookie(raise_if_no_session_exists=False)
|
||||||
|
new_infos = {"id": current_infos["id"]}
|
||||||
|
new_infos.update(infos)
|
||||||
|
|
||||||
|
response.set_cookie(
|
||||||
|
"yunohost.admin",
|
||||||
|
new_infos,
|
||||||
|
secure=True,
|
||||||
|
secret=session_secret,
|
||||||
|
httponly=True,
|
||||||
|
# samesite="strict", # Bottle 0.12 doesn't support samesite, to be added in next versions
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_session_cookie(self, raise_if_no_session_exists=True):
|
||||||
|
|
||||||
|
from bottle import request
|
||||||
|
|
||||||
|
try:
|
||||||
|
# N.B. : here we implicitly reauthenticate the cookie
|
||||||
|
# because it's signed via the session_secret
|
||||||
|
# If no session exists (or if session is invalid?)
|
||||||
|
# it's gonna return the default empty dict,
|
||||||
|
# which we interpret as an authentication failure
|
||||||
|
infos = request.get_cookie(
|
||||||
|
"yunohost.admin", secret=session_secret, default={}
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
if not raise_if_no_session_exists:
|
||||||
|
return {"id": random_ascii()}
|
||||||
|
raise YunohostAuthenticationError("unable_authenticate")
|
||||||
|
|
||||||
|
if not infos and raise_if_no_session_exists:
|
||||||
|
raise YunohostAuthenticationError("unable_authenticate")
|
||||||
|
|
||||||
|
if "id" not in infos:
|
||||||
|
infos["id"] = random_ascii()
|
||||||
|
|
||||||
|
# FIXME: Here, maybe we want to re-authenticate the session via the authenticator
|
||||||
|
# For example to check that the username authenticated is still in the admin group...
|
||||||
|
|
||||||
|
return infos
|
||||||
|
|
||||||
|
def delete_session_cookie(self):
|
||||||
|
|
||||||
|
from bottle import response
|
||||||
|
|
||||||
|
response.set_cookie("yunohost.admin", "", max_age=-1)
|
||||||
|
response.delete_cookie("yunohost.admin")
|
||||||
|
|
|
@ -75,7 +75,7 @@ from yunohost.utils.system import (
|
||||||
from yunohost.settings import settings_get
|
from yunohost.settings import settings_get
|
||||||
|
|
||||||
BACKUP_PATH = "/home/yunohost.backup"
|
BACKUP_PATH = "/home/yunohost.backup"
|
||||||
ARCHIVES_PATH = "%s/archives" % BACKUP_PATH
|
ARCHIVES_PATH = f"{BACKUP_PATH}/archives"
|
||||||
APP_MARGIN_SPACE_SIZE = 100 # In MB
|
APP_MARGIN_SPACE_SIZE = 100 # In MB
|
||||||
CONF_MARGIN_SPACE_SIZE = 10 # IN MB
|
CONF_MARGIN_SPACE_SIZE = 10 # IN MB
|
||||||
POSTINSTALL_ESTIMATE_SPACE_SIZE = 5 # In MB
|
POSTINSTALL_ESTIMATE_SPACE_SIZE = 5 # In MB
|
||||||
|
@ -405,7 +405,7 @@ class BackupManager:
|
||||||
# backup and restore scripts
|
# backup and restore scripts
|
||||||
|
|
||||||
for app in target_list:
|
for app in target_list:
|
||||||
app_script_folder = "/etc/yunohost/apps/%s/scripts" % app
|
app_script_folder = f"/etc/yunohost/apps/{app}/scripts"
|
||||||
backup_script_path = os.path.join(app_script_folder, "backup")
|
backup_script_path = os.path.join(app_script_folder, "backup")
|
||||||
restore_script_path = os.path.join(app_script_folder, "restore")
|
restore_script_path = os.path.join(app_script_folder, "restore")
|
||||||
|
|
||||||
|
@ -558,7 +558,7 @@ class BackupManager:
|
||||||
self._compute_backup_size()
|
self._compute_backup_size()
|
||||||
|
|
||||||
# Create backup info file
|
# Create backup info file
|
||||||
with open("%s/info.json" % self.work_dir, "w") as f:
|
with open(f"{self.work_dir}/info.json", "w") as f:
|
||||||
f.write(json.dumps(self.info))
|
f.write(json.dumps(self.info))
|
||||||
|
|
||||||
def _get_env_var(self, app=None):
|
def _get_env_var(self, app=None):
|
||||||
|
@ -735,7 +735,7 @@ class BackupManager:
|
||||||
logger.debug(m18n.n("backup_permission", app=app))
|
logger.debug(m18n.n("backup_permission", app=app))
|
||||||
permissions = user_permission_list(full=True, apps=[app])["permissions"]
|
permissions = user_permission_list(full=True, apps=[app])["permissions"]
|
||||||
this_app_permissions = {name: infos for name, infos in permissions.items()}
|
this_app_permissions = {name: infos for name, infos in permissions.items()}
|
||||||
write_to_yaml("%s/permissions.yml" % settings_dir, this_app_permissions)
|
write_to_yaml(f"{settings_dir}/permissions.yml", this_app_permissions)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(e)
|
logger.debug(e)
|
||||||
|
@ -924,7 +924,7 @@ class RestoreManager:
|
||||||
if not os.path.isfile("/etc/yunohost/installed"):
|
if not os.path.isfile("/etc/yunohost/installed"):
|
||||||
# Retrieve the domain from the backup
|
# Retrieve the domain from the backup
|
||||||
try:
|
try:
|
||||||
with open("%s/conf/ynh/current_host" % self.work_dir, "r") as f:
|
with open(f"{self.work_dir}/conf/ynh/current_host", "r") as f:
|
||||||
domain = f.readline().rstrip()
|
domain = f.readline().rstrip()
|
||||||
except IOError:
|
except IOError:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
@ -1007,7 +1007,7 @@ class RestoreManager:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
hook_paths = self.info["system"][system_part]["paths"]
|
hook_paths = self.info["system"][system_part]["paths"]
|
||||||
hook_paths = ["hooks/restore/%s" % os.path.basename(p) for p in hook_paths]
|
hook_paths = [f"hooks/restore/{os.path.basename(p)}" for p in hook_paths]
|
||||||
|
|
||||||
# Otherwise, add it from the archive to the system
|
# Otherwise, add it from the archive to the system
|
||||||
# FIXME: Refactor hook_add and use it instead
|
# FIXME: Refactor hook_add and use it instead
|
||||||
|
@ -1074,7 +1074,7 @@ class RestoreManager:
|
||||||
ret = subprocess.call(["umount", self.work_dir])
|
ret = subprocess.call(["umount", self.work_dir])
|
||||||
if ret == 0:
|
if ret == 0:
|
||||||
subprocess.call(["rmdir", self.work_dir])
|
subprocess.call(["rmdir", self.work_dir])
|
||||||
logger.debug("Unmount dir: {}".format(self.work_dir))
|
logger.debug(f"Unmount dir: {self.work_dir}")
|
||||||
else:
|
else:
|
||||||
raise YunohostError("restore_removing_tmp_dir_failed")
|
raise YunohostError("restore_removing_tmp_dir_failed")
|
||||||
elif os.path.isdir(self.work_dir):
|
elif os.path.isdir(self.work_dir):
|
||||||
|
@ -1083,7 +1083,7 @@ class RestoreManager:
|
||||||
)
|
)
|
||||||
ret = subprocess.call(["rm", "-Rf", self.work_dir])
|
ret = subprocess.call(["rm", "-Rf", self.work_dir])
|
||||||
if ret == 0:
|
if ret == 0:
|
||||||
logger.debug("Delete dir: {}".format(self.work_dir))
|
logger.debug(f"Delete dir: {self.work_dir}")
|
||||||
else:
|
else:
|
||||||
raise YunohostError("restore_removing_tmp_dir_failed")
|
raise YunohostError("restore_removing_tmp_dir_failed")
|
||||||
|
|
||||||
|
@ -1185,7 +1185,7 @@ class RestoreManager:
|
||||||
self._restore_apps()
|
self._restore_apps()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError(
|
raise YunohostError(
|
||||||
"The following critical error happened during restoration: %s" % e
|
f"The following critical error happened during restoration: {e}"
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
self.clean()
|
self.clean()
|
||||||
|
@ -1432,20 +1432,19 @@ class RestoreManager:
|
||||||
restore_script = os.path.join(tmp_workdir_for_app, "restore")
|
restore_script = os.path.join(tmp_workdir_for_app, "restore")
|
||||||
|
|
||||||
# Restore permissions
|
# Restore permissions
|
||||||
if not os.path.isfile("%s/permissions.yml" % app_settings_new_path):
|
if not os.path.isfile(f"{app_settings_new_path}/permissions.yml"):
|
||||||
raise YunohostError(
|
raise YunohostError(
|
||||||
"Didnt find a permssions.yml for the app !?", raw_msg=True
|
"Didnt find a permssions.yml for the app !?", raw_msg=True
|
||||||
)
|
)
|
||||||
|
|
||||||
permissions = read_yaml("%s/permissions.yml" % app_settings_new_path)
|
permissions = read_yaml(f"{app_settings_new_path}/permissions.yml")
|
||||||
existing_groups = user_group_list()["groups"]
|
existing_groups = user_group_list()["groups"]
|
||||||
|
|
||||||
for permission_name, permission_infos in permissions.items():
|
for permission_name, permission_infos in permissions.items():
|
||||||
|
|
||||||
if "allowed" not in permission_infos:
|
if "allowed" not in permission_infos:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"'allowed' key corresponding to allowed groups for permission %s not found when restoring app %s … You might have to reconfigure permissions yourself."
|
f"'allowed' key corresponding to allowed groups for permission {permission_name} not found when restoring app {app_instance_name} … You might have to reconfigure permissions yourself."
|
||||||
% (permission_name, app_instance_name)
|
|
||||||
)
|
)
|
||||||
should_be_allowed = ["all_users"]
|
should_be_allowed = ["all_users"]
|
||||||
else:
|
else:
|
||||||
|
@ -1470,7 +1469,7 @@ class RestoreManager:
|
||||||
|
|
||||||
permission_sync_to_user()
|
permission_sync_to_user()
|
||||||
|
|
||||||
os.remove("%s/permissions.yml" % app_settings_new_path)
|
os.remove(f"{app_settings_new_path}/permissions.yml")
|
||||||
|
|
||||||
_tools_migrations_run_before_app_restore(
|
_tools_migrations_run_before_app_restore(
|
||||||
backup_version=self.info["from_yunohost_version"],
|
backup_version=self.info["from_yunohost_version"],
|
||||||
|
@ -1819,8 +1818,7 @@ class BackupMethod:
|
||||||
# where everything is mapped to /dev/mapper/some-stuff
|
# where everything is mapped to /dev/mapper/some-stuff
|
||||||
# yet there are different devices behind it or idk ...
|
# yet there are different devices behind it or idk ...
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Could not link %s to %s (%s) ... falling back to regular copy."
|
f"Could not link {src} to {dest} ({e}) ... falling back to regular copy."
|
||||||
% (src, dest, str(e))
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Success, go to next file to organize
|
# Success, go to next file to organize
|
||||||
|
@ -2386,7 +2384,7 @@ def backup_list(with_info=False, human_readable=False):
|
||||||
"""
|
"""
|
||||||
# Get local archives sorted according to last modification time
|
# Get local archives sorted according to last modification time
|
||||||
# (we do a realpath() to resolve symlinks)
|
# (we do a realpath() to resolve symlinks)
|
||||||
archives = glob("%s/*.tar.gz" % ARCHIVES_PATH) + glob("%s/*.tar" % ARCHIVES_PATH)
|
archives = glob(f"{ARCHIVES_PATH}/*.tar.gz") + glob(f"{ARCHIVES_PATH}/*.tar")
|
||||||
archives = {os.path.realpath(archive) for archive in archives}
|
archives = {os.path.realpath(archive) for archive in archives}
|
||||||
archives = sorted(archives, key=lambda x: os.path.getctime(x))
|
archives = sorted(archives, key=lambda x: os.path.getctime(x))
|
||||||
# Extract only filename without the extension
|
# Extract only filename without the extension
|
||||||
|
@ -2408,10 +2406,9 @@ def backup_list(with_info=False, human_readable=False):
|
||||||
logger.warning(str(e))
|
logger.warning(str(e))
|
||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
import traceback
|
||||||
|
trace_ = "\n" + traceback.format_exc()
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Could not check infos for archive %s: %s"
|
f"Could not check infos for archive {archive}: {trace_}"
|
||||||
% (archive, "\n" + traceback.format_exc())
|
|
||||||
)
|
)
|
||||||
|
|
||||||
archives = d
|
archives = d
|
||||||
|
|
|
@ -228,10 +228,7 @@ def _certificate_install_selfsigned(domain_list, force=False):
|
||||||
)
|
)
|
||||||
operation_logger.success()
|
operation_logger.success()
|
||||||
else:
|
else:
|
||||||
msg = (
|
msg = f"Installation of self-signed certificate installation for {domain} failed !"
|
||||||
"Installation of self-signed certificate installation for %s failed !"
|
|
||||||
% (domain)
|
|
||||||
)
|
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
operation_logger.error(msg)
|
operation_logger.error(msg)
|
||||||
|
|
||||||
|
@ -299,8 +296,7 @@ def _certificate_install_letsencrypt(
|
||||||
operation_logger.error(msg)
|
operation_logger.error(msg)
|
||||||
if no_checks:
|
if no_checks:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Please consider checking the 'DNS records' (basic) and 'Web' categories of the diagnosis to check for possible issues that may prevent installing a Let's Encrypt certificate on domain %s."
|
f"Please consider checking the 'DNS records' (basic) and 'Web' categories of the diagnosis to check for possible issues that may prevent installing a Let's Encrypt certificate on domain {domain}."
|
||||||
% domain
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.success(m18n.n("certmanager_cert_install_success", domain=domain))
|
logger.success(m18n.n("certmanager_cert_install_success", domain=domain))
|
||||||
|
@ -417,11 +413,10 @@ def certificate_renew(
|
||||||
|
|
||||||
stack = StringIO()
|
stack = StringIO()
|
||||||
traceback.print_exc(file=stack)
|
traceback.print_exc(file=stack)
|
||||||
msg = "Certificate renewing for %s failed!" % (domain)
|
msg = f"Certificate renewing for {domain} failed!"
|
||||||
if no_checks:
|
if no_checks:
|
||||||
msg += (
|
msg += (
|
||||||
"\nPlease consider checking the 'DNS records' (basic) and 'Web' categories of the diagnosis to check for possible issues that may prevent installing a Let's Encrypt certificate on domain %s."
|
f"\nPlease consider checking the 'DNS records' (basic) and 'Web' categories of the diagnosis to check for possible issues that may prevent installing a Let's Encrypt certificate on domain {domain}."
|
||||||
% domain
|
|
||||||
)
|
)
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
operation_logger.error(msg)
|
operation_logger.error(msg)
|
||||||
|
@ -442,9 +437,9 @@ def certificate_renew(
|
||||||
|
|
||||||
|
|
||||||
def _email_renewing_failed(domain, exception_message, stack=""):
|
def _email_renewing_failed(domain, exception_message, stack=""):
|
||||||
from_ = "certmanager@%s (Certificate Manager)" % domain
|
from_ = f"certmanager@{domain} (Certificate Manager)"
|
||||||
to_ = "root"
|
to_ = "root"
|
||||||
subject_ = "Certificate renewing attempt for %s failed!" % domain
|
subject_ = f"Certificate renewing attempt for {domain} failed!"
|
||||||
|
|
||||||
logs = _tail(50, "/var/log/yunohost/yunohost-cli.log")
|
logs = _tail(50, "/var/log/yunohost/yunohost-cli.log")
|
||||||
message = f"""\
|
message = f"""\
|
||||||
|
@ -476,7 +471,7 @@ investigate :
|
||||||
|
|
||||||
def _check_acme_challenge_configuration(domain):
|
def _check_acme_challenge_configuration(domain):
|
||||||
|
|
||||||
domain_conf = "/etc/nginx/conf.d/%s.conf" % domain
|
domain_conf = f"/etc/nginx/conf.d/{domain}.conf"
|
||||||
return "include /etc/nginx/conf.d/acme-challenge.conf.inc" in read_file(domain_conf)
|
return "include /etc/nginx/conf.d/acme-challenge.conf.inc" in read_file(domain_conf)
|
||||||
|
|
||||||
|
|
||||||
|
@ -792,6 +787,9 @@ def _enable_certificate(domain, new_cert_folder):
|
||||||
logger.debug("Restarting services...")
|
logger.debug("Restarting services...")
|
||||||
|
|
||||||
for service in ("postfix", "dovecot", "metronome"):
|
for service in ("postfix", "dovecot", "metronome"):
|
||||||
|
# Ugly trick to not restart metronome if it's not installed
|
||||||
|
if service == "metronome" and os.system("dpkg --list | grep -q 'ii *metronome'") != 0:
|
||||||
|
continue
|
||||||
_run_service_command("restart", service)
|
_run_service_command("restart", service)
|
||||||
|
|
||||||
if os.path.isfile("/etc/yunohost/installed"):
|
if os.path.isfile("/etc/yunohost/installed"):
|
||||||
|
|
|
@ -188,7 +188,7 @@ def diagnosis_run(
|
||||||
# Call the hook ...
|
# Call the hook ...
|
||||||
diagnosed_categories = []
|
diagnosed_categories = []
|
||||||
for category in categories:
|
for category in categories:
|
||||||
logger.debug("Running diagnosis for %s ..." % category)
|
logger.debug(f"Running diagnosis for {category} ...")
|
||||||
|
|
||||||
diagnoser = _load_diagnoser(category)
|
diagnoser = _load_diagnoser(category)
|
||||||
|
|
||||||
|
@ -282,7 +282,7 @@ def _diagnosis_ignore(add_filter=None, remove_filter=None, list=False):
|
||||||
)
|
)
|
||||||
category = filter_[0]
|
category = filter_[0]
|
||||||
if category not in all_categories_names:
|
if category not in all_categories_names:
|
||||||
raise YunohostValidationError("%s is not a diagnosis category" % category)
|
raise YunohostValidationError(f"{category} is not a diagnosis category")
|
||||||
if any("=" not in criteria for criteria in filter_[1:]):
|
if any("=" not in criteria for criteria in filter_[1:]):
|
||||||
raise YunohostValidationError(
|
raise YunohostValidationError(
|
||||||
"Criterias should be of the form key=value (e.g. domain=yolo.test)"
|
"Criterias should be of the form key=value (e.g. domain=yolo.test)"
|
||||||
|
@ -423,7 +423,7 @@ class Diagnoser:
|
||||||
not force
|
not force
|
||||||
and self.cached_time_ago() < self.cache_duration
|
and self.cached_time_ago() < self.cache_duration
|
||||||
):
|
):
|
||||||
logger.debug("Cache still valid : %s" % self.cache_file)
|
logger.debug(f"Cache still valid : {self.cache_file}")
|
||||||
logger.info(
|
logger.info(
|
||||||
m18n.n("diagnosis_cache_still_valid", category=self.description)
|
m18n.n("diagnosis_cache_still_valid", category=self.description)
|
||||||
)
|
)
|
||||||
|
@ -457,7 +457,7 @@ class Diagnoser:
|
||||||
|
|
||||||
new_report = {"id": self.id_, "cached_for": self.cache_duration, "items": items}
|
new_report = {"id": self.id_, "cached_for": self.cache_duration, "items": items}
|
||||||
|
|
||||||
logger.debug("Updating cache %s" % self.cache_file)
|
logger.debug(f"Updating cache {self.cache_file}")
|
||||||
self.write_cache(new_report)
|
self.write_cache(new_report)
|
||||||
Diagnoser.i18n(new_report)
|
Diagnoser.i18n(new_report)
|
||||||
add_ignore_flag_to_issues(new_report)
|
add_ignore_flag_to_issues(new_report)
|
||||||
|
@ -530,7 +530,7 @@ class Diagnoser:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cache_file(id_):
|
def cache_file(id_):
|
||||||
return os.path.join(DIAGNOSIS_CACHE, "%s.json" % id_)
|
return os.path.join(DIAGNOSIS_CACHE, f"{id_}.json")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_cached_report(id_, item=None, warn_if_no_cache=True):
|
def get_cached_report(id_, item=None, warn_if_no_cache=True):
|
||||||
|
@ -633,7 +633,7 @@ class Diagnoser:
|
||||||
elif ipversion == 6:
|
elif ipversion == 6:
|
||||||
socket.getaddrinfo = getaddrinfo_ipv6_only
|
socket.getaddrinfo = getaddrinfo_ipv6_only
|
||||||
|
|
||||||
url = "https://{}/{}".format(DIAGNOSIS_SERVER, uri)
|
url = f"https://{DIAGNOSIS_SERVER}/{uri}"
|
||||||
try:
|
try:
|
||||||
r = requests.post(url, json=data, timeout=timeout)
|
r = requests.post(url, json=data, timeout=timeout)
|
||||||
finally:
|
finally:
|
||||||
|
@ -641,18 +641,16 @@ class Diagnoser:
|
||||||
|
|
||||||
if r.status_code not in [200, 400]:
|
if r.status_code not in [200, 400]:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"The remote diagnosis server failed miserably while trying to diagnose your server. This is most likely an error on Yunohost's infrastructure and not on your side. Please contact the YunoHost team an provide them with the following information.<br>URL: <code>%s</code><br>Status code: <code>%s</code>"
|
f"The remote diagnosis server failed miserably while trying to diagnose your server. This is most likely an error on Yunohost's infrastructure and not on your side. Please contact the YunoHost team an provide them with the following information.<br>URL: <code>{url}</code><br>Status code: <code>{r.status_code}</code>"
|
||||||
% (url, r.status_code)
|
|
||||||
)
|
)
|
||||||
if r.status_code == 400:
|
if r.status_code == 400:
|
||||||
raise Exception("Diagnosis request was refused: %s" % r.content)
|
raise Exception(f"Diagnosis request was refused: {r.content}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = r.json()
|
r = r.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
"Failed to parse json from diagnosis server response.\nError: %s\nOriginal content: %s"
|
f"Failed to parse json from diagnosis server response.\nError: {e}\nOriginal content: {r.content}"
|
||||||
% (e, r.content)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
@ -681,7 +679,7 @@ def _load_diagnoser(diagnoser_name):
|
||||||
# this is python builtin method to import a module using a name, we
|
# this is python builtin method to import a module using a name, we
|
||||||
# use that to import the migration as a python object so we'll be
|
# use that to import the migration as a python object so we'll be
|
||||||
# able to run it in the next loop
|
# able to run it in the next loop
|
||||||
module = import_module("yunohost.diagnosers.{}".format(module_id))
|
module = import_module(f"yunohost.diagnosers.{module_id}")
|
||||||
return module.MyDiagnoser()
|
return module.MyDiagnoser()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
import traceback
|
import traceback
|
||||||
|
@ -695,9 +693,9 @@ def _email_diagnosis_issues():
|
||||||
from yunohost.domain import _get_maindomain
|
from yunohost.domain import _get_maindomain
|
||||||
|
|
||||||
maindomain = _get_maindomain()
|
maindomain = _get_maindomain()
|
||||||
from_ = "diagnosis@{} (Automatic diagnosis on {})".format(maindomain, maindomain)
|
from_ = f"diagnosis@{maindomain} (Automatic diagnosis on {maindomain})"
|
||||||
to_ = "root"
|
to_ = "root"
|
||||||
subject_ = "Issues found by automatic diagnosis on %s" % maindomain
|
subject_ = f"Issues found by automatic diagnosis on {maindomain}"
|
||||||
|
|
||||||
disclaimer = "The automatic diagnosis on your YunoHost server identified some issues on your server. You will find a description of the issues below. You can manage those issues in the 'Diagnosis' section in your webadmin."
|
disclaimer = "The automatic diagnosis on your YunoHost server identified some issues on your server. You will find a description of the issues below. You can manage those issues in the 'Diagnosis' section in your webadmin."
|
||||||
|
|
||||||
|
@ -707,23 +705,17 @@ def _email_diagnosis_issues():
|
||||||
|
|
||||||
content = _dump_human_readable_reports(issues)
|
content = _dump_human_readable_reports(issues)
|
||||||
|
|
||||||
message = """\
|
message = f"""\
|
||||||
From: {}
|
From: {from_}
|
||||||
To: {}
|
To: {to_}
|
||||||
Subject: {}
|
Subject: {subject_}
|
||||||
|
|
||||||
{}
|
{disclaimer}
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
{}
|
{content}
|
||||||
""".format(
|
"""
|
||||||
from_,
|
|
||||||
to_,
|
|
||||||
subject_,
|
|
||||||
disclaimer,
|
|
||||||
content,
|
|
||||||
)
|
|
||||||
|
|
||||||
import smtplib
|
import smtplib
|
||||||
|
|
||||||
|
|
|
@ -338,7 +338,7 @@ def _build_dns_conf(base_domain, include_empty_AAAA_if_no_ipv6=False):
|
||||||
|
|
||||||
|
|
||||||
def _get_DKIM(domain):
|
def _get_DKIM(domain):
|
||||||
DKIM_file = "/etc/dkim/{domain}.mail.txt".format(domain=domain)
|
DKIM_file = f"/etc/dkim/{domain}.mail.txt"
|
||||||
|
|
||||||
if not os.path.isfile(DKIM_file):
|
if not os.path.isfile(DKIM_file):
|
||||||
return (None, None)
|
return (None, None)
|
||||||
|
|
|
@ -69,7 +69,7 @@ def domain_list(exclude_subdomains=False):
|
||||||
result = [
|
result = [
|
||||||
entry["virtualdomain"][0]
|
entry["virtualdomain"][0]
|
||||||
for entry in ldap.search(
|
for entry in ldap.search(
|
||||||
"ou=domains,dc=yunohost,dc=org", "virtualdomain=*", ["virtualdomain"]
|
"ou=domains", "virtualdomain=*", ["virtualdomain"]
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ def domain_add(operation_logger, domain, dyndns=False):
|
||||||
# Actually subscribe
|
# Actually subscribe
|
||||||
dyndns_subscribe(domain=domain)
|
dyndns_subscribe(domain=domain)
|
||||||
|
|
||||||
_certificate_install_selfsigned([domain], False)
|
_certificate_install_selfsigned([domain], True)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
attr_dict = {
|
attr_dict = {
|
||||||
|
@ -196,7 +196,7 @@ def domain_add(operation_logger, domain, dyndns=False):
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ldap.add("virtualdomain=%s,ou=domains" % domain, attr_dict)
|
ldap.add(f"virtualdomain={domain},ou=domains", attr_dict)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError("domain_creation_failed", domain=domain, error=e)
|
raise YunohostError("domain_creation_failed", domain=domain, error=e)
|
||||||
finally:
|
finally:
|
||||||
|
@ -215,7 +215,7 @@ def domain_add(operation_logger, domain, dyndns=False):
|
||||||
# This is a pretty ad hoc solution and only applied to nginx
|
# This is a pretty ad hoc solution and only applied to nginx
|
||||||
# because it's one of the major service, but in the long term we
|
# because it's one of the major service, but in the long term we
|
||||||
# should identify the root of this bug...
|
# should identify the root of this bug...
|
||||||
_force_clear_hashes(["/etc/nginx/conf.d/%s.conf" % domain])
|
_force_clear_hashes([f"/etc/nginx/conf.d/{domain}.conf"])
|
||||||
regen_conf(
|
regen_conf(
|
||||||
names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd", "mdns"]
|
names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd", "mdns"]
|
||||||
)
|
)
|
||||||
|
@ -282,8 +282,7 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False):
|
||||||
apps_on_that_domain.append(
|
apps_on_that_domain.append(
|
||||||
(
|
(
|
||||||
app,
|
app,
|
||||||
' - %s "%s" on https://%s%s'
|
f" - {app} \"{label}\" on https://{domain}{settings['path']}"
|
||||||
% (app, label, domain, settings["path"])
|
|
||||||
if "path" in settings
|
if "path" in settings
|
||||||
else app,
|
else app,
|
||||||
)
|
)
|
||||||
|
@ -342,14 +341,14 @@ def domain_remove(operation_logger, domain, remove_apps=False, force=False):
|
||||||
# This is a pretty ad hoc solution and only applied to nginx
|
# This is a pretty ad hoc solution and only applied to nginx
|
||||||
# because it's one of the major service, but in the long term we
|
# because it's one of the major service, but in the long term we
|
||||||
# should identify the root of this bug...
|
# should identify the root of this bug...
|
||||||
_force_clear_hashes(["/etc/nginx/conf.d/%s.conf" % domain])
|
_force_clear_hashes([f"/etc/nginx/conf.d/{domain}.conf"])
|
||||||
# And in addition we even force-delete the file Otherwise, if the file was
|
# And in addition we even force-delete the file Otherwise, if the file was
|
||||||
# manually modified, it may not get removed by the regenconf which leads to
|
# manually modified, it may not get removed by the regenconf which leads to
|
||||||
# catastrophic consequences of nginx breaking because it can't load the
|
# catastrophic consequences of nginx breaking because it can't load the
|
||||||
# cert file which disappeared etc..
|
# cert file which disappeared etc..
|
||||||
if os.path.exists("/etc/nginx/conf.d/%s.conf" % domain):
|
if os.path.exists(f"/etc/nginx/conf.d/{domain}.conf"):
|
||||||
_process_regen_conf(
|
_process_regen_conf(
|
||||||
"/etc/nginx/conf.d/%s.conf" % domain, new_conf=None, save=True
|
f"/etc/nginx/conf.d/{domain}.conf", new_conf=None, save=True
|
||||||
)
|
)
|
||||||
|
|
||||||
regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd", "mdns"])
|
regen_conf(names=["nginx", "metronome", "dnsmasq", "postfix", "rspamd", "mdns"])
|
||||||
|
@ -388,7 +387,7 @@ def domain_main_domain(operation_logger, new_main_domain=None):
|
||||||
domain_list_cache = {}
|
domain_list_cache = {}
|
||||||
_set_hostname(new_main_domain)
|
_set_hostname(new_main_domain)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("%s" % e, exc_info=1)
|
logger.warning(str(e), exc_info=1)
|
||||||
raise YunohostError("main_domain_change_failed")
|
raise YunohostError("main_domain_change_failed")
|
||||||
|
|
||||||
# Generate SSOwat configuration file
|
# Generate SSOwat configuration file
|
||||||
|
@ -517,9 +516,7 @@ def domain_cert_install(
|
||||||
):
|
):
|
||||||
from yunohost.certificate import certificate_install
|
from yunohost.certificate import certificate_install
|
||||||
|
|
||||||
return certificate_install(
|
return certificate_install(domain_list, force, no_checks, self_signed, staging)
|
||||||
domain_list, force, no_checks, self_signed, staging
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def domain_cert_renew(
|
def domain_cert_renew(
|
||||||
|
@ -527,9 +524,7 @@ def domain_cert_renew(
|
||||||
):
|
):
|
||||||
from yunohost.certificate import certificate_renew
|
from yunohost.certificate import certificate_renew
|
||||||
|
|
||||||
return certificate_renew(
|
return certificate_renew(domain_list, force, no_checks, email, staging)
|
||||||
domain_list, force, no_checks, email, staging
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def domain_dns_conf(domain):
|
def domain_dns_conf(domain):
|
||||||
|
|
103
src/dyndns.py
103
src/dyndns.py
|
@ -33,7 +33,7 @@ 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 write_to_file, read_file, rm, chown, chmod
|
from moulinette.utils.filesystem import write_to_file, rm, chown, chmod
|
||||||
from moulinette.utils.network import download_json
|
from moulinette.utils.network import download_json
|
||||||
|
|
||||||
from yunohost.utils.error import YunohostError, YunohostValidationError
|
from yunohost.utils.error import YunohostError, YunohostValidationError
|
||||||
|
@ -45,14 +45,6 @@ from yunohost.regenconf import regen_conf
|
||||||
|
|
||||||
logger = getActionLogger("yunohost.dyndns")
|
logger = getActionLogger("yunohost.dyndns")
|
||||||
|
|
||||||
DYNDNS_ZONE = "/etc/yunohost/dyndns/zone"
|
|
||||||
|
|
||||||
RE_DYNDNS_PRIVATE_KEY_MD5 = re.compile(r".*/K(?P<domain>[^\s\+]+)\.\+157.+\.private$")
|
|
||||||
|
|
||||||
RE_DYNDNS_PRIVATE_KEY_SHA512 = re.compile(
|
|
||||||
r".*/K(?P<domain>[^\s\+]+)\.\+165.+\.private$"
|
|
||||||
)
|
|
||||||
|
|
||||||
DYNDNS_PROVIDER = "dyndns.yunohost.org"
|
DYNDNS_PROVIDER = "dyndns.yunohost.org"
|
||||||
DYNDNS_DNS_AUTH = ["ns0.yunohost.org", "ns1.yunohost.org"]
|
DYNDNS_DNS_AUTH = ["ns0.yunohost.org", "ns1.yunohost.org"]
|
||||||
|
|
||||||
|
@ -111,6 +103,10 @@ def dyndns_subscribe(operation_logger, domain=None, key=None):
|
||||||
|
|
||||||
operation_logger.start()
|
operation_logger.start()
|
||||||
|
|
||||||
|
# '165' is the convention identifier for hmac-sha512 algorithm
|
||||||
|
# '1234' is idk? doesnt matter, but the old format contained a number here...
|
||||||
|
key_file = f"/etc/yunohost/dyndns/K{domain}.+165+1234.key"
|
||||||
|
|
||||||
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:
|
||||||
if not os.path.exists("/etc/yunohost/dyndns"):
|
if not os.path.exists("/etc/yunohost/dyndns"):
|
||||||
|
@ -118,35 +114,39 @@ def dyndns_subscribe(operation_logger, domain=None, key=None):
|
||||||
|
|
||||||
logger.debug(m18n.n("dyndns_key_generating"))
|
logger.debug(m18n.n("dyndns_key_generating"))
|
||||||
|
|
||||||
os.system(
|
# Here, we emulate the behavior of the old 'dnssec-keygen' utility
|
||||||
"cd /etc/yunohost/dyndns && "
|
# which since bullseye was replaced by ddns-keygen which is now
|
||||||
f"dnssec-keygen -a hmac-sha512 -b 512 -r /dev/urandom -n USER {domain}"
|
# in the bind9 package ... but installing bind9 will conflict with dnsmasq
|
||||||
)
|
# and is just madness just to have access to a tsig keygen utility -.-
|
||||||
|
|
||||||
|
# Use 512 // 8 = 64 bytes for hmac-sha512 (c.f. https://git.hactrn.net/sra/tsig-keygen/src/master/tsig-keygen.py)
|
||||||
|
secret = base64.b64encode(os.urandom(512 // 8)).decode("ascii")
|
||||||
|
|
||||||
|
# Idk why but the secret is split in two parts, with the first one
|
||||||
|
# being 57-long char ... probably some DNS format
|
||||||
|
secret = f"{secret[:56]} {secret[56:]}"
|
||||||
|
|
||||||
|
key_content = f"{domain}. IN KEY 0 3 165 {secret}"
|
||||||
|
write_to_file(key_file, key_content)
|
||||||
|
|
||||||
chmod("/etc/yunohost/dyndns", 0o600, recursive=True)
|
chmod("/etc/yunohost/dyndns", 0o600, recursive=True)
|
||||||
chown("/etc/yunohost/dyndns", "root", recursive=True)
|
chown("/etc/yunohost/dyndns", "root", recursive=True)
|
||||||
|
|
||||||
private_file = glob.glob("/etc/yunohost/dyndns/*%s*.private" % domain)[0]
|
|
||||||
key_file = glob.glob("/etc/yunohost/dyndns/*%s*.key" % domain)[0]
|
|
||||||
with open(key_file) as f:
|
|
||||||
key = f.readline().strip().split(" ", 6)[-1]
|
|
||||||
|
|
||||||
import requests # lazy loading this module for performance reasons
|
import requests # lazy loading this module for performance reasons
|
||||||
|
|
||||||
# Send subscription
|
# Send subscription
|
||||||
try:
|
try:
|
||||||
b64encoded_key = base64.b64encode(key.encode()).decode()
|
# Yeah the secret is already a base64-encoded but we double-bas64-encode it, whatever...
|
||||||
|
b64encoded_key = base64.b64encode(secret.encode()).decode()
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
f"https://{DYNDNS_PROVIDER}/key/{b64encoded_key}?key_algo=hmac-sha512",
|
f"https://{DYNDNS_PROVIDER}/key/{b64encoded_key}?key_algo=hmac-sha512",
|
||||||
data={"subdomain": domain},
|
data={"subdomain": domain},
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
rm(private_file, force=True)
|
|
||||||
rm(key_file, force=True)
|
rm(key_file, force=True)
|
||||||
raise YunohostError("dyndns_registration_failed", error=str(e))
|
raise YunohostError("dyndns_registration_failed", error=str(e))
|
||||||
if r.status_code != 201:
|
if r.status_code != 201:
|
||||||
rm(private_file, force=True)
|
|
||||||
rm(key_file, force=True)
|
rm(key_file, force=True)
|
||||||
try:
|
try:
|
||||||
error = json.loads(r.text)["error"]
|
error = json.loads(r.text)["error"]
|
||||||
|
@ -154,7 +154,7 @@ def dyndns_subscribe(operation_logger, domain=None, key=None):
|
||||||
error = f'Server error, code: {r.status_code}. (Message: "{r.text}")'
|
error = f'Server error, code: {r.status_code}. (Message: "{r.text}")'
|
||||||
raise YunohostError("dyndns_registration_failed", error=error)
|
raise YunohostError("dyndns_registration_failed", error=error)
|
||||||
|
|
||||||
# Yunohost regen conf will add the dyndns cron job if a private key exists
|
# Yunohost regen conf will add the dyndns cron job if a key exists
|
||||||
# in /etc/yunohost/dyndns
|
# in /etc/yunohost/dyndns
|
||||||
regen_conf(["yunohost"])
|
regen_conf(["yunohost"])
|
||||||
|
|
||||||
|
@ -185,6 +185,11 @@ def dyndns_update(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from yunohost.dns import _build_dns_conf
|
from yunohost.dns import _build_dns_conf
|
||||||
|
import dns.query
|
||||||
|
import dns.tsig
|
||||||
|
import dns.tsigkeyring
|
||||||
|
import dns.update
|
||||||
|
|
||||||
|
|
||||||
# If domain is not given, try to guess it from keys available...
|
# If domain is not given, try to guess it from keys available...
|
||||||
key = None
|
key = None
|
||||||
|
@ -196,7 +201,7 @@ def dyndns_update(
|
||||||
|
|
||||||
# If key is not given, pick the first file we find with the domain given
|
# If key is not given, pick the first file we find with the domain given
|
||||||
elif key is None:
|
elif key is None:
|
||||||
keys = glob.glob(f"/etc/yunohost/dyndns/K{domain}.+*.private")
|
keys = glob.glob(f"/etc/yunohost/dyndns/K{domain}.+*.key")
|
||||||
|
|
||||||
if not keys:
|
if not keys:
|
||||||
raise YunohostValidationError("dyndns_key_not_found")
|
raise YunohostValidationError("dyndns_key_not_found")
|
||||||
|
@ -217,12 +222,14 @@ def dyndns_update(
|
||||||
host = domain.split(".")[1:]
|
host = domain.split(".")[1:]
|
||||||
host = ".".join(host)
|
host = ".".join(host)
|
||||||
|
|
||||||
logger.debug("Building zone update file ...")
|
logger.debug("Building zone update ...")
|
||||||
|
|
||||||
lines = [
|
with open(key) as f:
|
||||||
f"server {DYNDNS_PROVIDER}",
|
key = f.readline().strip().split(" ", 6)[-1]
|
||||||
f"zone {host}",
|
|
||||||
]
|
keyring = dns.tsigkeyring.from_text({f'{domain}.': key})
|
||||||
|
# Python's dns.update is similar to the old nsupdate cli tool
|
||||||
|
update = dns.update.Update(domain, keyring=keyring, keyalgorithm=dns.tsig.HMAC_SHA512)
|
||||||
|
|
||||||
auth_resolvers = []
|
auth_resolvers = []
|
||||||
|
|
||||||
|
@ -293,9 +300,8 @@ def dyndns_update(
|
||||||
# [{"name": "...", "ttl": "...", "type": "...", "value": "..."}]
|
# [{"name": "...", "ttl": "...", "type": "...", "value": "..."}]
|
||||||
for records in dns_conf.values():
|
for records in dns_conf.values():
|
||||||
for record in records:
|
for record in records:
|
||||||
action = "update delete {name}.{domain}.".format(domain=domain, **record)
|
name = f"{record['name']}.{domain}." if record['name'] != "@" else f"{domain}."
|
||||||
action = action.replace(" @.", " ")
|
update.delete(name)
|
||||||
lines.append(action)
|
|
||||||
|
|
||||||
# Add the new records for all domain/subdomains
|
# Add the new records for all domain/subdomains
|
||||||
|
|
||||||
|
@ -307,32 +313,22 @@ def dyndns_update(
|
||||||
if record["value"] == "@":
|
if record["value"] == "@":
|
||||||
record["value"] = domain
|
record["value"] = domain
|
||||||
record["value"] = record["value"].replace(";", r"\;")
|
record["value"] = record["value"].replace(";", r"\;")
|
||||||
|
name = f"{record['name']}.{domain}." if record['name'] != "@" else f"{domain}."
|
||||||
|
|
||||||
action = "update add {name}.{domain}. {ttl} {type} {value}".format(
|
update.add(name, record['ttl'], record['type'], record['value'])
|
||||||
domain=domain, **record
|
|
||||||
)
|
|
||||||
action = action.replace(" @.", " ")
|
|
||||||
lines.append(action)
|
|
||||||
|
|
||||||
lines += ["show", "send"]
|
|
||||||
|
|
||||||
# Write the actions to do to update to a file, to be able to pass it
|
|
||||||
# to nsupdate as argument
|
|
||||||
write_to_file(DYNDNS_ZONE, "\n".join(lines))
|
|
||||||
|
|
||||||
logger.debug("Now pushing new conf to DynDNS host...")
|
logger.debug("Now pushing new conf to DynDNS host...")
|
||||||
|
logger.debug(update)
|
||||||
|
|
||||||
if not dry_run:
|
if not dry_run:
|
||||||
try:
|
try:
|
||||||
command = ["/usr/bin/nsupdate", "-k", key, DYNDNS_ZONE]
|
dns.query.tcp(update, auth_resolvers[0])
|
||||||
subprocess.check_call(command)
|
except Exception as e:
|
||||||
except subprocess.CalledProcessError:
|
logger.error(e)
|
||||||
raise YunohostError("dyndns_ip_update_failed")
|
raise YunohostError("dyndns_ip_update_failed")
|
||||||
|
|
||||||
logger.success(m18n.n("dyndns_ip_updated"))
|
logger.success(m18n.n("dyndns_ip_updated"))
|
||||||
else:
|
else:
|
||||||
print(read_file(DYNDNS_ZONE))
|
|
||||||
print("")
|
|
||||||
print(
|
print(
|
||||||
"Warning: dry run, this is only the generated config, it won't be applied"
|
"Warning: dry run, this is only the generated config, it won't be applied"
|
||||||
)
|
)
|
||||||
|
@ -347,15 +343,16 @@ def _guess_current_dyndns_domain():
|
||||||
dynette...)
|
dynette...)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
DYNDNS_KEY_REGEX = re.compile(
|
||||||
|
r".*/K(?P<domain>[^\s\+]+)\.\+165.+\.key$"
|
||||||
|
)
|
||||||
|
|
||||||
# Retrieve the first registered domain
|
# Retrieve the first registered domain
|
||||||
paths = list(glob.iglob("/etc/yunohost/dyndns/K*.private"))
|
paths = list(glob.iglob("/etc/yunohost/dyndns/K*.key"))
|
||||||
for path in paths:
|
for path in paths:
|
||||||
# MD5 is legacy ugh
|
match = DYNDNS_KEY_REGEX.match(path)
|
||||||
match = RE_DYNDNS_PRIVATE_KEY_MD5.match(path)
|
|
||||||
if not match:
|
if not match:
|
||||||
match = RE_DYNDNS_PRIVATE_KEY_SHA512.match(path)
|
continue
|
||||||
if not match:
|
|
||||||
continue
|
|
||||||
_domain = match.group("domain")
|
_domain = match.group("domain")
|
||||||
|
|
||||||
# Verify if domain is registered (i.e., if it's available, skip
|
# Verify if domain is registered (i.e., if it's available, skip
|
||||||
|
|
|
@ -95,7 +95,7 @@ def hook_info(action, name):
|
||||||
priorities = set()
|
priorities = set()
|
||||||
|
|
||||||
# Search in custom folder first
|
# Search in custom folder first
|
||||||
for h in iglob("{:s}{:s}/*-{:s}".format(CUSTOM_HOOK_FOLDER, action, name)):
|
for h in iglob(f"{CUSTOM_HOOK_FOLDER}{action}/*-{name}"):
|
||||||
priority, _ = _extract_filename_parts(os.path.basename(h))
|
priority, _ = _extract_filename_parts(os.path.basename(h))
|
||||||
priorities.add(priority)
|
priorities.add(priority)
|
||||||
hooks.append(
|
hooks.append(
|
||||||
|
@ -105,7 +105,7 @@ def hook_info(action, name):
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# Append non-overwritten system hooks
|
# Append non-overwritten system hooks
|
||||||
for h in iglob("{:s}{:s}/*-{:s}".format(HOOK_FOLDER, action, name)):
|
for h in iglob(f"{HOOK_FOLDER}{action}/*-{name}"):
|
||||||
priority, _ = _extract_filename_parts(os.path.basename(h))
|
priority, _ = _extract_filename_parts(os.path.basename(h))
|
||||||
if priority not in priorities:
|
if priority not in priorities:
|
||||||
hooks.append(
|
hooks.append(
|
||||||
|
@ -431,8 +431,7 @@ def _hook_exec_bash(path, args, chdir, env, user, return_format, loggers):
|
||||||
|
|
||||||
# use xtrace on fd 7 which is redirected to stdout
|
# use xtrace on fd 7 which is redirected to stdout
|
||||||
env["BASH_XTRACEFD"] = "7"
|
env["BASH_XTRACEFD"] = "7"
|
||||||
cmd = '/bin/bash -x "{script}" {args} 7>&1'
|
command.append(f'/bin/bash -x "{cmd_script}" {cmd_args} 7>&1')
|
||||||
command.append(cmd.format(script=cmd_script, args=cmd_args))
|
|
||||||
|
|
||||||
logger.debug("Executing command '%s'" % command)
|
logger.debug("Executing command '%s'" % command)
|
||||||
|
|
||||||
|
|
|
@ -659,6 +659,11 @@ class OperationLogger:
|
||||||
data["error"] = self._error
|
data["error"] = self._error
|
||||||
# TODO: detect if 'extra' erase some key of 'data'
|
# TODO: detect if 'extra' erase some key of 'data'
|
||||||
data.update(self.extra)
|
data.update(self.extra)
|
||||||
|
# Remove the 'args' arg from args (yodawg). It corresponds to url-encoded args for app install, config panel set, etc
|
||||||
|
# Because the data are url encoded, it's hell to properly redact secrets inside it,
|
||||||
|
# and the useful info is usually already available in `env` too
|
||||||
|
if "args" in data and isinstance(data["args"], dict) and "args" in data["args"]:
|
||||||
|
data["args"].pop("args")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def success(self):
|
def success(self):
|
||||||
|
|
|
@ -7,7 +7,12 @@ from moulinette.utils.log import getActionLogger
|
||||||
from moulinette.utils.process import check_output, call_async_output
|
from moulinette.utils.process import check_output, call_async_output
|
||||||
from moulinette.utils.filesystem import read_file, rm
|
from moulinette.utils.filesystem import read_file, rm
|
||||||
|
|
||||||
from yunohost.tools import Migration, tools_update, tools_upgrade
|
from yunohost.tools import (
|
||||||
|
Migration,
|
||||||
|
tools_update,
|
||||||
|
tools_upgrade,
|
||||||
|
_apt_log_line_is_relevant,
|
||||||
|
)
|
||||||
from yunohost.app import unstable_apps
|
from yunohost.app import unstable_apps
|
||||||
from yunohost.regenconf import manually_modified_files, _force_clear_hashes
|
from yunohost.regenconf import manually_modified_files, _force_clear_hashes
|
||||||
from yunohost.utils.filesystem import free_space_in_directory
|
from yunohost.utils.filesystem import free_space_in_directory
|
||||||
|
@ -50,6 +55,25 @@ class MyMigration(Migration):
|
||||||
#
|
#
|
||||||
logger.info(m18n.n("migration_0021_patching_sources_list"))
|
logger.info(m18n.n("migration_0021_patching_sources_list"))
|
||||||
self.patch_apt_sources_list()
|
self.patch_apt_sources_list()
|
||||||
|
|
||||||
|
# Force add sury if it's not there yet
|
||||||
|
# This is to solve some weird issue with php-common breaking php7.3-common,
|
||||||
|
# hence breaking many php7.3-deps
|
||||||
|
# hence triggering some dependency conflict (or foobar-ynh-deps uninstall)
|
||||||
|
# Adding it there shouldnt be a big deal - Yunohost 11.x does add it
|
||||||
|
# through its regen conf anyway.
|
||||||
|
if not os.path.exists("/etc/apt/sources.list.d/extra_php_version.list"):
|
||||||
|
open("/etc/apt/sources.list.d/extra_php_version.list", "w").write(
|
||||||
|
"deb https://packages.sury.org/php/ bullseye main"
|
||||||
|
)
|
||||||
|
os.system(
|
||||||
|
'wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"'
|
||||||
|
)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Run apt update
|
||||||
|
#
|
||||||
|
|
||||||
tools_update(target="system")
|
tools_update(target="system")
|
||||||
|
|
||||||
# Tell libc6 it's okay to restart system stuff during the upgrade
|
# Tell libc6 it's okay to restart system stuff during the upgrade
|
||||||
|
@ -77,9 +101,12 @@ class MyMigration(Migration):
|
||||||
_force_clear_hashes(["/etc/mysql/my.cnf"])
|
_force_clear_hashes(["/etc/mysql/my.cnf"])
|
||||||
rm("/etc/mysql/mariadb.cnf", force=True)
|
rm("/etc/mysql/mariadb.cnf", force=True)
|
||||||
rm("/etc/mysql/my.cnf", force=True)
|
rm("/etc/mysql/my.cnf", force=True)
|
||||||
self.apt_install(
|
ret = self.apt_install(
|
||||||
"mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'"
|
"mariadb-common --reinstall -o Dpkg::Options::='--force-confmiss'"
|
||||||
)
|
)
|
||||||
|
if ret != 0:
|
||||||
|
# FIXME: i18n once this is stable?
|
||||||
|
raise YunohostError("Failed to reinstall mariadb-common ?", raw_msg=True)
|
||||||
|
|
||||||
#
|
#
|
||||||
# /usr/share/yunohost/yunohost-config/ssl/yunoCA -> /usr/share/yunohost/ssl
|
# /usr/share/yunohost/yunohost-config/ssl/yunoCA -> /usr/share/yunohost/ssl
|
||||||
|
@ -97,6 +124,13 @@ class MyMigration(Migration):
|
||||||
os.system("mv /home/yunohost.conf /var/cache/yunohost/regenconf")
|
os.system("mv /home/yunohost.conf /var/cache/yunohost/regenconf")
|
||||||
rm("/home/yunohost.conf", recursive=True, force=True)
|
rm("/home/yunohost.conf", recursive=True, force=True)
|
||||||
|
|
||||||
|
# Remove legacy postgresql service record added by helpers,
|
||||||
|
# will now be dynamically handled by the core in bullseye
|
||||||
|
services = _get_services()
|
||||||
|
if "postgresql" in services:
|
||||||
|
del services["postgresql"]
|
||||||
|
_save_services(services)
|
||||||
|
|
||||||
#
|
#
|
||||||
# Main upgrade
|
# Main upgrade
|
||||||
#
|
#
|
||||||
|
@ -109,43 +143,114 @@ class MyMigration(Migration):
|
||||||
if self.debian_major_version() == N_CURRENT_DEBIAN:
|
if self.debian_major_version() == N_CURRENT_DEBIAN:
|
||||||
raise YunohostError("migration_0021_still_on_buster_after_main_upgrade")
|
raise YunohostError("migration_0021_still_on_buster_after_main_upgrade")
|
||||||
|
|
||||||
|
# Force explicit install of php7.4-fpm and other old 'default' dependencies
|
||||||
|
# that are now only in Recommends
|
||||||
|
#
|
||||||
|
# Also, we need to install php7.4 equivalents of other php7.3 dependencies.
|
||||||
|
# For example, Nextcloud may depend on php7.3-zip, and after the php pool migration
|
||||||
|
# to autoupgrade Nextcloud to 7.4, it will need the php7.4-zip to work.
|
||||||
|
# The following list is based on an ad-hoc analysis of php deps found in the
|
||||||
|
# app ecosystem, with a known equivalent on php7.4.
|
||||||
|
#
|
||||||
|
# This is kinda a dirty hack as it doesnt properly update the *-ynh-deps virtual packages
|
||||||
|
# with the proper list of dependencies, and the dependencies install this way
|
||||||
|
# will get flagged as 'manually installed'.
|
||||||
|
#
|
||||||
|
# We'll probably want to do something during the Bullseye->Bookworm migration to re-flag
|
||||||
|
# these as 'auto' so they get autoremoved if not needed anymore.
|
||||||
|
# Also hopefully by then we'll have manifestv2 (maybe) and will be able to use
|
||||||
|
# the apt resource mecanism to regenerate the *-ynh-deps virtual packages ;)
|
||||||
|
|
||||||
|
php73packages_suffixes = [
|
||||||
|
"apcu",
|
||||||
|
"bcmath",
|
||||||
|
"bz2",
|
||||||
|
"dom",
|
||||||
|
"gmp",
|
||||||
|
"igbinary",
|
||||||
|
"imagick",
|
||||||
|
"imap",
|
||||||
|
"mbstring",
|
||||||
|
"memcached",
|
||||||
|
"mysqli",
|
||||||
|
"mysqlnd",
|
||||||
|
"pgsql",
|
||||||
|
"redis",
|
||||||
|
"simplexml",
|
||||||
|
"soap",
|
||||||
|
"sqlite3",
|
||||||
|
"ssh2",
|
||||||
|
"tidy",
|
||||||
|
"xml",
|
||||||
|
"xmlrpc",
|
||||||
|
"xsl",
|
||||||
|
"zip",
|
||||||
|
]
|
||||||
|
|
||||||
|
cmd = (
|
||||||
|
"apt show '*-ynh-deps' 2>/dev/null"
|
||||||
|
" | grep Depends"
|
||||||
|
f" | grep -o -E \"php7.3-({'|'.join(php73packages_suffixes)})\""
|
||||||
|
" | sort | uniq"
|
||||||
|
" | sed 's/php7.3/php7.4/g'"
|
||||||
|
" || true"
|
||||||
|
)
|
||||||
|
|
||||||
|
basephp74packages_to_install = [
|
||||||
|
"php7.4-fpm",
|
||||||
|
"php7.4-common",
|
||||||
|
"php7.4-ldap",
|
||||||
|
"php7.4-intl",
|
||||||
|
"php7.4-mysql",
|
||||||
|
"php7.4-gd",
|
||||||
|
"php7.4-curl",
|
||||||
|
"php-php-gettext",
|
||||||
|
]
|
||||||
|
|
||||||
|
php74packages_to_install = basephp74packages_to_install + [
|
||||||
|
f.strip() for f in check_output(cmd).split("\n") if f.strip()
|
||||||
|
]
|
||||||
|
|
||||||
|
ret = self.apt_install(
|
||||||
|
f"{' '.join(php74packages_to_install)} "
|
||||||
|
"$(dpkg --list | grep ynh-deps | awk '{print $2}') "
|
||||||
|
"-o Dpkg::Options::='--force-confmiss'"
|
||||||
|
)
|
||||||
|
if ret != 0:
|
||||||
|
# FIXME: i18n once this is stable?
|
||||||
|
raise YunohostError(
|
||||||
|
"Failed to force the install of php dependencies ?", raw_msg=True
|
||||||
|
)
|
||||||
|
|
||||||
|
os.system(f"apt-mark auto {' '.join(basephp74packages_to_install)}")
|
||||||
|
|
||||||
# Clean the mess
|
# Clean the mess
|
||||||
logger.info(m18n.n("migration_0021_cleaning_up"))
|
logger.info(m18n.n("migration_0021_cleaning_up"))
|
||||||
os.system("apt autoremove --assume-yes")
|
os.system("apt autoremove --assume-yes")
|
||||||
os.system("apt clean --assume-yes")
|
os.system("apt clean --assume-yes")
|
||||||
|
|
||||||
# Force add sury if it's not there yet
|
|
||||||
# This is to solve some weird issue with php-common breaking php7.3-common,
|
|
||||||
# hence breaking many php7.3-deps
|
|
||||||
# hence triggering some dependency conflict (or foobar-ynh-deps uninstall)
|
|
||||||
# Adding it there shouldnt be a big deal - Yunohost 11.x does add it
|
|
||||||
# through its regen conf anyway.
|
|
||||||
if not os.path.exists("/etc/apt/sources.list.d/extra_php_version.list"):
|
|
||||||
open("/etc/apt/sources.list.d/extra_php_version.list", "w").write(
|
|
||||||
"deb https://packages.sury.org/php/ bullseye main"
|
|
||||||
)
|
|
||||||
os.system(
|
|
||||||
'wget --timeout 900 --quiet "https://packages.sury.org/php/apt.gpg" --output-document=- | gpg --dearmor >"/etc/apt/trusted.gpg.d/extra_php_version.gpg"'
|
|
||||||
)
|
|
||||||
|
|
||||||
os.system("apt update")
|
|
||||||
|
|
||||||
# Force explicit install of php7.4-fpm to make sure it's ll be there
|
|
||||||
# during 0022_php73_to_php74_pools migration
|
|
||||||
self.apt_install("php7.4-fpm -o Dpkg::Options::='--force-confmiss'")
|
|
||||||
|
|
||||||
# Remove legacy postgresql service record added by helpers,
|
|
||||||
# will now be dynamically handled by the core in bullseye
|
|
||||||
services = _get_services()
|
|
||||||
if "postgresql" in services:
|
|
||||||
del services["postgresql"]
|
|
||||||
_save_services(services)
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Yunohost upgrade
|
# Yunohost upgrade
|
||||||
#
|
#
|
||||||
logger.info(m18n.n("migration_0021_yunohost_upgrade"))
|
logger.info(m18n.n("migration_0021_yunohost_upgrade"))
|
||||||
|
|
||||||
self.unhold(apps_packages)
|
self.unhold(apps_packages)
|
||||||
|
|
||||||
|
cmd = "LC_ALL=C"
|
||||||
|
cmd += " DEBIAN_FRONTEND=noninteractive"
|
||||||
|
cmd += " APT_LISTCHANGES_FRONTEND=none"
|
||||||
|
cmd += " apt dist-upgrade "
|
||||||
|
cmd += " --quiet -o=Dpkg::Use-Pty=0 --fix-broken --dry-run"
|
||||||
|
cmd += " | grep -q 'ynh-deps'"
|
||||||
|
|
||||||
|
logger.info("Simulating upgrade...")
|
||||||
|
if os.system(cmd) == 0:
|
||||||
|
# FIXME: i18n once this is stable?
|
||||||
|
raise YunohostError(
|
||||||
|
"The upgrade cannot be completed, because some app dependencies would need to be removed?",
|
||||||
|
raw_msg=True,
|
||||||
|
)
|
||||||
|
|
||||||
tools_upgrade(target="system")
|
tools_upgrade(target="system")
|
||||||
|
|
||||||
def debian_major_version(self):
|
def debian_major_version(self):
|
||||||
|
@ -268,11 +373,11 @@ class MyMigration(Migration):
|
||||||
|
|
||||||
def hold(self, packages):
|
def hold(self, packages):
|
||||||
for package in packages:
|
for package in packages:
|
||||||
os.system("apt-mark hold {}".format(package))
|
os.system(f"apt-mark hold {package}")
|
||||||
|
|
||||||
def unhold(self, packages):
|
def unhold(self, packages):
|
||||||
for package in packages:
|
for package in packages:
|
||||||
os.system("apt-mark unhold {}".format(package))
|
os.system(f"apt-mark unhold {package}")
|
||||||
|
|
||||||
def apt_install(self, cmd):
|
def apt_install(self, cmd):
|
||||||
def is_relevant(line):
|
def is_relevant(line):
|
||||||
|
@ -280,9 +385,11 @@ class MyMigration(Migration):
|
||||||
|
|
||||||
callbacks = (
|
callbacks = (
|
||||||
lambda l: logger.info("+ " + l.rstrip() + "\r")
|
lambda l: logger.info("+ " + l.rstrip() + "\r")
|
||||||
if is_relevant(l)
|
if _apt_log_line_is_relevant(l)
|
||||||
else logger.debug(l.rstrip() + "\r"),
|
else logger.debug(l.rstrip() + "\r"),
|
||||||
lambda l: logger.warning(l.rstrip()),
|
lambda l: logger.warning(l.rstrip())
|
||||||
|
if _apt_log_line_is_relevant(l)
|
||||||
|
else logger.debug(l.rstrip()),
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd = (
|
cmd = (
|
||||||
|
@ -292,7 +399,7 @@ class MyMigration(Migration):
|
||||||
|
|
||||||
logger.debug("Running: %s" % cmd)
|
logger.debug("Running: %s" % cmd)
|
||||||
|
|
||||||
call_async_output(cmd, callbacks, shell=True)
|
return call_async_output(cmd, callbacks, shell=True)
|
||||||
|
|
||||||
def patch_yunohost_conflicts(self):
|
def patch_yunohost_conflicts(self):
|
||||||
#
|
#
|
||||||
|
|
|
@ -58,7 +58,7 @@ def user_permission_list(
|
||||||
|
|
||||||
ldap = _get_ldap_interface()
|
ldap = _get_ldap_interface()
|
||||||
permissions_infos = ldap.search(
|
permissions_infos = ldap.search(
|
||||||
"ou=permission,dc=yunohost,dc=org",
|
"ou=permission",
|
||||||
"(objectclass=permissionYnh)",
|
"(objectclass=permissionYnh)",
|
||||||
[
|
[
|
||||||
"cn",
|
"cn",
|
||||||
|
@ -133,8 +133,7 @@ def user_permission_list(
|
||||||
main_perm_name = name.split(".")[0] + ".main"
|
main_perm_name = name.split(".")[0] + ".main"
|
||||||
if main_perm_name not in permissions:
|
if main_perm_name not in permissions:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Uhoh, unknown permission %s ? (Maybe we're in the process or deleting the perm for this app...)"
|
f"Uhoh, unknown permission {main_perm_name} ? (Maybe we're in the process or deleting the perm for this app...)"
|
||||||
% main_perm_name
|
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
main_perm_label = permissions[main_perm_name]["label"]
|
main_perm_label = permissions[main_perm_name]["label"]
|
||||||
|
@ -408,7 +407,7 @@ def permission_create(
|
||||||
|
|
||||||
# Validate uniqueness of permission in LDAP
|
# Validate uniqueness of permission in LDAP
|
||||||
if ldap.get_conflict(
|
if ldap.get_conflict(
|
||||||
{"cn": permission}, base_dn="ou=permission,dc=yunohost,dc=org"
|
{"cn": permission}, base_dn="ou=permission"
|
||||||
):
|
):
|
||||||
raise YunohostValidationError("permission_already_exist", permission=permission)
|
raise YunohostValidationError("permission_already_exist", permission=permission)
|
||||||
|
|
||||||
|
@ -452,7 +451,7 @@ def permission_create(
|
||||||
operation_logger.start()
|
operation_logger.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ldap.add("cn=%s,ou=permission" % permission, attr_dict)
|
ldap.add(f"cn={permission},ou=permission", attr_dict)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError(
|
raise YunohostError(
|
||||||
"permission_creation_failed", permission=permission, error=e
|
"permission_creation_failed", permission=permission, error=e
|
||||||
|
@ -585,7 +584,7 @@ def permission_url(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ldap.update(
|
ldap.update(
|
||||||
"cn=%s,ou=permission" % permission,
|
f"cn={permission},ou=permission",
|
||||||
{
|
{
|
||||||
"URL": [url] if url is not None else [],
|
"URL": [url] if url is not None else [],
|
||||||
"additionalUrls": new_additional_urls,
|
"additionalUrls": new_additional_urls,
|
||||||
|
@ -633,7 +632,7 @@ def permission_delete(operation_logger, permission, force=False, sync_perm=True)
|
||||||
operation_logger.start()
|
operation_logger.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ldap.remove("cn=%s,ou=permission" % permission)
|
ldap.remove(f"cn={permission},ou=permission")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError(
|
raise YunohostError(
|
||||||
"permission_deletion_failed", permission=permission, error=e
|
"permission_deletion_failed", permission=permission, error=e
|
||||||
|
@ -679,7 +678,7 @@ def permission_sync_to_user():
|
||||||
|
|
||||||
new_inherited_perms = {
|
new_inherited_perms = {
|
||||||
"inheritPermission": [
|
"inheritPermission": [
|
||||||
"uid=%s,ou=users,dc=yunohost,dc=org" % u
|
f"uid={u},ou=users,dc=yunohost,dc=org"
|
||||||
for u in should_be_allowed_users
|
for u in should_be_allowed_users
|
||||||
],
|
],
|
||||||
"memberUid": should_be_allowed_users,
|
"memberUid": should_be_allowed_users,
|
||||||
|
@ -687,7 +686,7 @@ def permission_sync_to_user():
|
||||||
|
|
||||||
# Commit the change with the new inherited stuff
|
# Commit the change with the new inherited stuff
|
||||||
try:
|
try:
|
||||||
ldap.update("cn=%s,ou=permission" % permission_name, new_inherited_perms)
|
ldap.update(f"cn={permission_name},ou=permission", new_inherited_perms)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError(
|
raise YunohostError(
|
||||||
"permission_update_failed", permission=permission_name, error=e
|
"permission_update_failed", permission=permission_name, error=e
|
||||||
|
@ -765,7 +764,7 @@ def _update_ldap_group_permission(
|
||||||
update["showTile"] = [str(show_tile).upper()]
|
update["showTile"] = [str(show_tile).upper()]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ldap.update("cn=%s,ou=permission" % permission, update)
|
ldap.update(f"cn={permission},ou=permission", update)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError("permission_update_failed", permission=permission, error=e)
|
raise YunohostError("permission_update_failed", permission=permission, error=e)
|
||||||
|
|
||||||
|
|
|
@ -449,7 +449,7 @@ def _save_regenconf_infos(infos):
|
||||||
yaml.safe_dump(infos, f, default_flow_style=False)
|
yaml.safe_dump(infos, f, default_flow_style=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Error while saving regenconf infos, exception: %s", e, exc_info=1
|
f"Error while saving regenconf infos, exception: {e}", exc_info=1
|
||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
@ -506,7 +506,7 @@ def _calculate_hash(path):
|
||||||
|
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Error while calculating file '%s' hash: %s", path, e, exc_info=1
|
f"Error while calculating file '{path}' hash: {e}", exc_info=1
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -559,11 +559,11 @@ def _get_conf_hashes(category):
|
||||||
categories = _get_regenconf_infos()
|
categories = _get_regenconf_infos()
|
||||||
|
|
||||||
if category not in categories:
|
if category not in categories:
|
||||||
logger.debug("category %s is not in categories.yml yet.", category)
|
logger.debug(f"category {category} is not in categories.yml yet.")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
elif categories[category] is None or "conffiles" not in categories[category]:
|
elif categories[category] is None or "conffiles" not in categories[category]:
|
||||||
logger.debug("No configuration files for category %s.", category)
|
logger.debug(f"No configuration files for category {category}.")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
@ -572,7 +572,7 @@ def _get_conf_hashes(category):
|
||||||
|
|
||||||
def _update_conf_hashes(category, hashes):
|
def _update_conf_hashes(category, hashes):
|
||||||
"""Update the registered conf hashes for a category"""
|
"""Update the registered conf hashes for a category"""
|
||||||
logger.debug("updating conf hashes for '%s' with: %s", category, hashes)
|
logger.debug(f"updating conf hashes for '{category}' with: {hashes}")
|
||||||
|
|
||||||
categories = _get_regenconf_infos()
|
categories = _get_regenconf_infos()
|
||||||
category_conf = categories.get(category, {})
|
category_conf = categories.get(category, {})
|
||||||
|
@ -603,8 +603,7 @@ def _force_clear_hashes(paths):
|
||||||
for category in categories.keys():
|
for category in categories.keys():
|
||||||
if path in categories[category]["conffiles"]:
|
if path in categories[category]["conffiles"]:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"force-clearing old conf hash for %s in category %s"
|
f"force-clearing old conf hash for {path} in category {category}"
|
||||||
% (path, category)
|
|
||||||
)
|
)
|
||||||
del categories[category]["conffiles"][path]
|
del categories[category]["conffiles"][path]
|
||||||
|
|
||||||
|
@ -647,9 +646,7 @@ def _process_regen_conf(system_conf, new_conf=None, save=True):
|
||||||
logger.debug(m18n.n("regenconf_file_updated", conf=system_conf))
|
logger.debug(m18n.n("regenconf_file_updated", conf=system_conf))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Exception while trying to regenerate conf '%s': %s",
|
f"Exception while trying to regenerate conf '{system_conf}': {e}",
|
||||||
system_conf,
|
|
||||||
e,
|
|
||||||
exc_info=1,
|
exc_info=1,
|
||||||
)
|
)
|
||||||
if not new_conf and os.path.exists(system_conf):
|
if not new_conf and os.path.exists(system_conf):
|
||||||
|
|
|
@ -407,8 +407,7 @@ def _get_and_format_service_status(service, infos):
|
||||||
|
|
||||||
if raw_status is None:
|
if raw_status is None:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Failed to get status information via dbus for service %s, systemctl didn't recognize this service ('NoSuchUnit')."
|
f"Failed to get status information via dbus for service {systemd_service}, systemctl didn't recognize this service ('NoSuchUnit')."
|
||||||
% systemd_service
|
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
"status": "unknown",
|
"status": "unknown",
|
||||||
|
@ -424,7 +423,7 @@ def _get_and_format_service_status(service, infos):
|
||||||
# If no description was there, try to get it from the .json locales
|
# If no description was there, try to get it from the .json locales
|
||||||
if not description:
|
if not description:
|
||||||
|
|
||||||
translation_key = "service_description_%s" % service
|
translation_key = f"service_description_{service}"
|
||||||
if m18n.key_exists(translation_key):
|
if m18n.key_exists(translation_key):
|
||||||
description = m18n.n(translation_key)
|
description = m18n.n(translation_key)
|
||||||
else:
|
else:
|
||||||
|
@ -445,7 +444,7 @@ def _get_and_format_service_status(service, infos):
|
||||||
"enabled" if glob("/etc/rc[S5].d/S??" + service) else "disabled"
|
"enabled" if glob("/etc/rc[S5].d/S??" + service) else "disabled"
|
||||||
)
|
)
|
||||||
elif os.path.exists(
|
elif os.path.exists(
|
||||||
"/etc/systemd/system/multi-user.target.wants/%s.service" % service
|
f"/etc/systemd/system/multi-user.target.wants/{service}.service"
|
||||||
):
|
):
|
||||||
output["start_on_boot"] = "enabled"
|
output["start_on_boot"] = "enabled"
|
||||||
|
|
||||||
|
@ -585,8 +584,7 @@ def _run_service_command(action, service):
|
||||||
]
|
]
|
||||||
if action not in possible_actions:
|
if action not in possible_actions:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Unknown action '%s', available actions are: %s"
|
f"Unknown action '{action}', available actions are: {', '.join(possible_actions)}"
|
||||||
% (action, ", ".join(possible_actions))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd = f"systemctl {action} {service}"
|
cmd = f"systemctl {action} {service}"
|
||||||
|
@ -604,7 +602,7 @@ def _run_service_command(action, service):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Launch the command
|
# Launch the command
|
||||||
logger.debug("Running '%s'" % cmd)
|
logger.debug(f"Running '{cmd}'")
|
||||||
p = subprocess.Popen(cmd.split(), stderr=subprocess.STDOUT)
|
p = subprocess.Popen(cmd.split(), stderr=subprocess.STDOUT)
|
||||||
# If this command needs a lock (because the service uses yunohost
|
# If this command needs a lock (because the service uses yunohost
|
||||||
# commands inside), find the PID and add a lock for it
|
# commands inside), find the PID and add a lock for it
|
||||||
|
@ -651,7 +649,7 @@ def _give_lock(action, service, p):
|
||||||
if son_PID != 0:
|
if son_PID != 0:
|
||||||
# Append the PID to the lock file
|
# Append the PID to the lock file
|
||||||
logger.debug(f"Giving a lock to PID {son_PID} for service {service} !")
|
logger.debug(f"Giving a lock to PID {son_PID} for service {service} !")
|
||||||
append_to_file(MOULINETTE_LOCK, "\n%s" % str(son_PID))
|
append_to_file(MOULINETTE_LOCK, f"\n{son_PID}")
|
||||||
|
|
||||||
return son_PID
|
return son_PID
|
||||||
|
|
||||||
|
@ -815,7 +813,7 @@ def _find_previous_log_file(file):
|
||||||
i = int(i[0]) + 1 if len(i) > 0 else 1
|
i = int(i[0]) + 1 if len(i) > 0 else 1
|
||||||
|
|
||||||
previous_file = file if i == 1 else splitext[0]
|
previous_file = file if i == 1 else splitext[0]
|
||||||
previous_file = previous_file + ".%d" % (i)
|
previous_file = previous_file + f".{i}"
|
||||||
if os.path.exists(previous_file):
|
if os.path.exists(previous_file):
|
||||||
return previous_file
|
return previous_file
|
||||||
|
|
||||||
|
@ -835,8 +833,7 @@ def _get_journalctl_logs(service, number="all"):
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
import traceback
|
import traceback
|
||||||
|
trace_ = traceback.format_exc()
|
||||||
return (
|
return (
|
||||||
"error while get services logs from journalctl:\n%s"
|
f"error while get services logs from journalctl:\n{trace_}"
|
||||||
% traceback.format_exc()
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -81,6 +81,10 @@ DEFAULTS = OrderedDict(
|
||||||
"security.ssh.port",
|
"security.ssh.port",
|
||||||
{"type": "int", "default": 22},
|
{"type": "int", "default": 22},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"security.ssh.password_authentication",
|
||||||
|
{"type": "bool", "default": True},
|
||||||
|
),
|
||||||
(
|
(
|
||||||
"security.nginx.redirect_to_https",
|
"security.nginx.redirect_to_https",
|
||||||
{
|
{
|
||||||
|
@ -281,7 +285,7 @@ def settings_reset_all():
|
||||||
|
|
||||||
|
|
||||||
def _get_setting_description(key):
|
def _get_setting_description(key):
|
||||||
return m18n.n("global_settings_setting_%s" % key.replace(".", "_"))
|
return m18n.n(f"global_settings_setting_{key}".replace(".", "_"))
|
||||||
|
|
||||||
|
|
||||||
def _get_settings():
|
def _get_settings():
|
||||||
|
@ -311,7 +315,7 @@ def _get_settings():
|
||||||
try:
|
try:
|
||||||
unknown_settings = json.load(open(unknown_settings_path, "r"))
|
unknown_settings = json.load(open(unknown_settings_path, "r"))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("Error while loading unknown settings %s" % e)
|
logger.warning(f"Error while loading unknown settings {e}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(SETTINGS_PATH) as settings_fd:
|
with open(SETTINGS_PATH) as settings_fd:
|
||||||
|
@ -338,7 +342,7 @@ def _get_settings():
|
||||||
_save_settings(settings)
|
_save_settings(settings)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Failed to save unknown settings (because %s), aborting." % e
|
f"Failed to save unknown settings (because {e}), aborting."
|
||||||
)
|
)
|
||||||
|
|
||||||
return settings
|
return settings
|
||||||
|
@ -370,11 +374,10 @@ post_change_hooks = {}
|
||||||
def post_change_hook(setting_name):
|
def post_change_hook(setting_name):
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
assert setting_name in DEFAULTS.keys(), (
|
assert setting_name in DEFAULTS.keys(), (
|
||||||
"The setting %s does not exists" % setting_name
|
f"The setting {setting_name} does not exists"
|
||||||
)
|
)
|
||||||
assert setting_name not in post_change_hooks, (
|
assert setting_name not in post_change_hooks, (
|
||||||
"You can only register one post change hook per setting (in particular for %s)"
|
f"You can only register one post change hook per setting (in particular for {setting_name})"
|
||||||
% setting_name
|
|
||||||
)
|
)
|
||||||
post_change_hooks[setting_name] = func
|
post_change_hooks[setting_name] = func
|
||||||
return func
|
return func
|
||||||
|
@ -384,7 +387,7 @@ def post_change_hook(setting_name):
|
||||||
|
|
||||||
def trigger_post_change_hook(setting_name, old_value, new_value):
|
def trigger_post_change_hook(setting_name, old_value, new_value):
|
||||||
if setting_name not in post_change_hooks:
|
if setting_name not in post_change_hooks:
|
||||||
logger.debug("Nothing to do after changing setting %s" % setting_name)
|
logger.debug(f"Nothing to do after changing setting {setting_name}")
|
||||||
return
|
return
|
||||||
|
|
||||||
f = post_change_hooks[setting_name]
|
f = post_change_hooks[setting_name]
|
||||||
|
@ -420,6 +423,7 @@ def reconfigure_nginx_and_yunohost(setting_name, old_value, new_value):
|
||||||
|
|
||||||
|
|
||||||
@post_change_hook("security.ssh.compatibility")
|
@post_change_hook("security.ssh.compatibility")
|
||||||
|
@post_change_hook("security.ssh.password_authentication")
|
||||||
def reconfigure_ssh(setting_name, old_value, new_value):
|
def reconfigure_ssh(setting_name, old_value, new_value):
|
||||||
if old_value != new_value:
|
if old_value != new_value:
|
||||||
regen_conf(names=["ssh"])
|
regen_conf(names=["ssh"])
|
||||||
|
|
|
@ -99,7 +99,7 @@ def user_ssh_remove_key(username, key):
|
||||||
|
|
||||||
if not os.path.exists(authorized_keys_file):
|
if not os.path.exists(authorized_keys_file):
|
||||||
raise YunohostValidationError(
|
raise YunohostValidationError(
|
||||||
"this key doesn't exists ({} dosesn't exists)".format(authorized_keys_file),
|
f"this key doesn't exists ({authorized_keys_file} dosesn't exists)",
|
||||||
raw_msg=True,
|
raw_msg=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ def user_ssh_remove_key(username, key):
|
||||||
|
|
||||||
if key not in authorized_keys_content:
|
if key not in authorized_keys_content:
|
||||||
raise YunohostValidationError(
|
raise YunohostValidationError(
|
||||||
"Key '{}' is not present in authorized_keys".format(key), raw_msg=True
|
f"Key '{key}' is not present in authorized_keys", raw_msg=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# don't delete the previous comment because we can't verify if it's legit
|
# don't delete the previous comment because we can't verify if it's legit
|
||||||
|
@ -172,7 +172,7 @@ def _get_user_for_ssh(username, attrs=None):
|
||||||
|
|
||||||
ldap = _get_ldap_interface()
|
ldap = _get_ldap_interface()
|
||||||
user = ldap.search(
|
user = ldap.search(
|
||||||
"ou=users,dc=yunohost,dc=org",
|
"ou=users",
|
||||||
"(&(objectclass=person)(uid=%s))" % username,
|
"(&(objectclass=person)(uid=%s))" % username,
|
||||||
attrs,
|
attrs,
|
||||||
)
|
)
|
||||||
|
|
|
@ -236,17 +236,17 @@ def check_LDAP_db_integrity():
|
||||||
ldap = _get_ldap_interface()
|
ldap = _get_ldap_interface()
|
||||||
|
|
||||||
user_search = ldap.search(
|
user_search = ldap.search(
|
||||||
"ou=users,dc=yunohost,dc=org",
|
"ou=users",
|
||||||
"(&(objectclass=person)(!(uid=root))(!(uid=nobody)))",
|
"(&(objectclass=person)(!(uid=root))(!(uid=nobody)))",
|
||||||
["uid", "memberOf", "permission"],
|
["uid", "memberOf", "permission"],
|
||||||
)
|
)
|
||||||
group_search = ldap.search(
|
group_search = ldap.search(
|
||||||
"ou=groups,dc=yunohost,dc=org",
|
"ou=groups",
|
||||||
"(objectclass=groupOfNamesYnh)",
|
"(objectclass=groupOfNamesYnh)",
|
||||||
["cn", "member", "memberUid", "permission"],
|
["cn", "member", "memberUid", "permission"],
|
||||||
)
|
)
|
||||||
permission_search = ldap.search(
|
permission_search = ldap.search(
|
||||||
"ou=permission,dc=yunohost,dc=org",
|
"ou=permission",
|
||||||
"(objectclass=permissionYnh)",
|
"(objectclass=permissionYnh)",
|
||||||
["cn", "groupPermission", "inheritPermission", "memberUid"],
|
["cn", "groupPermission", "inheritPermission", "memberUid"],
|
||||||
)
|
)
|
||||||
|
|
46
src/tools.py
46
src/tools.py
|
@ -33,7 +33,7 @@ from typing import List
|
||||||
|
|
||||||
from moulinette import Moulinette, m18n
|
from moulinette import Moulinette, m18n
|
||||||
from moulinette.utils.log import getActionLogger
|
from moulinette.utils.log import getActionLogger
|
||||||
from moulinette.utils.process import check_output, call_async_output
|
from moulinette.utils.process import call_async_output
|
||||||
from moulinette.utils.filesystem import read_yaml, write_to_yaml, cp, mkdir, rm
|
from moulinette.utils.filesystem import read_yaml, write_to_yaml, cp, mkdir, rm
|
||||||
|
|
||||||
from yunohost.app import (
|
from yunohost.app import (
|
||||||
|
@ -101,7 +101,7 @@ def tools_adminpw(new_password, check_strength=True):
|
||||||
{"userPassword": [new_hash]},
|
{"userPassword": [new_hash]},
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("unable to change admin password : %s" % e)
|
logger.error(f"unable to change admin password : {e}")
|
||||||
raise YunohostError("admin_password_change_failed")
|
raise YunohostError("admin_password_change_failed")
|
||||||
else:
|
else:
|
||||||
# Write as root password
|
# Write as root password
|
||||||
|
@ -148,7 +148,7 @@ def _set_hostname(hostname, pretty_hostname=None):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not pretty_hostname:
|
if not pretty_hostname:
|
||||||
pretty_hostname = "(YunoHost/%s)" % hostname
|
pretty_hostname = f"(YunoHost/{hostname})"
|
||||||
|
|
||||||
# First clear nsswitch cache for hosts to make sure hostname is resolved...
|
# First clear nsswitch cache for hosts to make sure hostname is resolved...
|
||||||
subprocess.call(["nscd", "-i", "hosts"])
|
subprocess.call(["nscd", "-i", "hosts"])
|
||||||
|
@ -232,9 +232,7 @@ def tools_postinstall(
|
||||||
# and inform the user that we could not contact the dyndns host server.
|
# and inform the user that we could not contact the dyndns host server.
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
m18n.n(
|
m18n.n("dyndns_provider_unreachable", provider="dyndns.yunohost.org")
|
||||||
"dyndns_provider_unreachable", provider="dyndns.yunohost.org"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if available:
|
if available:
|
||||||
|
@ -322,7 +320,7 @@ def tools_update(target=None):
|
||||||
|
|
||||||
if target not in ["system", "apps", "all"]:
|
if target not in ["system", "apps", "all"]:
|
||||||
raise YunohostError(
|
raise YunohostError(
|
||||||
"Unknown target %s, should be 'system', 'apps' or 'all'" % target,
|
f"Unknown target {target}, should be 'system', 'apps' or 'all'",
|
||||||
raw_msg=True,
|
raw_msg=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -468,7 +466,7 @@ def tools_upgrade(
|
||||||
try:
|
try:
|
||||||
app_upgrade(app=upgradable_apps)
|
app_upgrade(app=upgradable_apps)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning("unable to upgrade apps: %s" % str(e))
|
logger.warning(f"unable to upgrade apps: {e}")
|
||||||
logger.error(m18n.n("app_upgrade_some_app_failed"))
|
logger.error(m18n.n("app_upgrade_some_app_failed"))
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -513,10 +511,10 @@ def tools_upgrade(
|
||||||
|
|
||||||
callbacks = (
|
callbacks = (
|
||||||
lambda l: logger.info("+ " + l.rstrip() + "\r")
|
lambda l: logger.info("+ " + l.rstrip() + "\r")
|
||||||
if is_relevant(l)
|
if _apt_log_line_is_relevant(l)
|
||||||
else logger.debug(l.rstrip() + "\r"),
|
else logger.debug(l.rstrip() + "\r"),
|
||||||
lambda l: logger.warning(l.rstrip())
|
lambda l: logger.warning(l.rstrip())
|
||||||
if is_relevant(l)
|
if _apt_log_line_is_relevant(l)
|
||||||
else logger.debug(l.rstrip()),
|
else logger.debug(l.rstrip()),
|
||||||
)
|
)
|
||||||
returncode = call_async_output(dist_upgrade, callbacks, shell=True)
|
returncode = call_async_output(dist_upgrade, callbacks, shell=True)
|
||||||
|
@ -550,6 +548,24 @@ def tools_upgrade(
|
||||||
operation_logger.success()
|
operation_logger.success()
|
||||||
|
|
||||||
|
|
||||||
|
def _apt_log_line_is_relevant(line):
|
||||||
|
irrelevants = [
|
||||||
|
"service sudo-ldap already provided",
|
||||||
|
"Reading database ...",
|
||||||
|
"Preparing to unpack",
|
||||||
|
"Selecting previously unselected package",
|
||||||
|
"Created symlink /etc/systemd",
|
||||||
|
"Replacing config file",
|
||||||
|
"Creating config file",
|
||||||
|
"Installing new version of config file",
|
||||||
|
"Installing new config file as you requested",
|
||||||
|
", does not exist on system.",
|
||||||
|
"unable to delete old directory",
|
||||||
|
"update-alternatives:",
|
||||||
|
]
|
||||||
|
return line.rstrip() and all(i not in line.rstrip() for i in irrelevants)
|
||||||
|
|
||||||
|
|
||||||
@is_unit_operation()
|
@is_unit_operation()
|
||||||
def tools_shutdown(operation_logger, force=False):
|
def tools_shutdown(operation_logger, force=False):
|
||||||
shutdown = force
|
shutdown = force
|
||||||
|
@ -856,7 +872,7 @@ def _get_migration_by_name(migration_name):
|
||||||
try:
|
try:
|
||||||
from . import migrations
|
from . import migrations
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise AssertionError("Unable to find migration with name %s" % migration_name)
|
raise AssertionError(f"Unable to find migration with name {migration_name}")
|
||||||
|
|
||||||
migrations_path = migrations.__path__[0]
|
migrations_path = migrations.__path__[0]
|
||||||
migrations_found = [
|
migrations_found = [
|
||||||
|
@ -866,7 +882,7 @@ def _get_migration_by_name(migration_name):
|
||||||
]
|
]
|
||||||
|
|
||||||
assert len(migrations_found) == 1, (
|
assert len(migrations_found) == 1, (
|
||||||
"Unable to find migration with name %s" % migration_name
|
f"Unable to find migration with name {migration_name}"
|
||||||
)
|
)
|
||||||
|
|
||||||
return _load_migration(migrations_found[0])
|
return _load_migration(migrations_found[0])
|
||||||
|
@ -903,6 +919,10 @@ def _skip_all_migrations():
|
||||||
all_migrations = _get_migrations_list()
|
all_migrations = _get_migrations_list()
|
||||||
new_states = {"migrations": {}}
|
new_states = {"migrations": {}}
|
||||||
for migration in all_migrations:
|
for migration in all_migrations:
|
||||||
|
# Don't skip bullseye migration while we're
|
||||||
|
# still on buster
|
||||||
|
if "migrate_to_bullseye" in migration.id:
|
||||||
|
continue
|
||||||
new_states["migrations"][migration.id] = "skipped"
|
new_states["migrations"][migration.id] = "skipped"
|
||||||
write_to_yaml(MIGRATIONS_STATE_PATH, new_states)
|
write_to_yaml(MIGRATIONS_STATE_PATH, new_states)
|
||||||
|
|
||||||
|
@ -986,7 +1006,7 @@ class Migration:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
return m18n.n("migration_description_%s" % self.id)
|
return m18n.n(f"migration_description_{self.id}")
|
||||||
|
|
||||||
def ldap_migration(self, run):
|
def ldap_migration(self, run):
|
||||||
def func(self):
|
def func(self):
|
||||||
|
|
50
src/user.py
50
src/user.py
|
@ -112,7 +112,7 @@ def user_list(fields=None):
|
||||||
|
|
||||||
ldap = _get_ldap_interface()
|
ldap = _get_ldap_interface()
|
||||||
result = ldap.search(
|
result = ldap.search(
|
||||||
"ou=users,dc=yunohost,dc=org",
|
"ou=users",
|
||||||
"(&(objectclass=person)(!(uid=root))(!(uid=nobody)))",
|
"(&(objectclass=person)(!(uid=root))(!(uid=nobody)))",
|
||||||
attrs,
|
attrs,
|
||||||
)
|
)
|
||||||
|
@ -164,7 +164,7 @@ def user_create(
|
||||||
|
|
||||||
maindomain = _get_maindomain()
|
maindomain = _get_maindomain()
|
||||||
domain = Moulinette.prompt(
|
domain = Moulinette.prompt(
|
||||||
m18n.n("ask_user_domain") + " (default: %s)" % maindomain
|
m18n.n("ask_user_domain") + f" (default: {maindomain})"
|
||||||
)
|
)
|
||||||
if not domain:
|
if not domain:
|
||||||
domain = maindomain
|
domain = maindomain
|
||||||
|
@ -234,11 +234,11 @@ def user_create(
|
||||||
}
|
}
|
||||||
|
|
||||||
# If it is the first user, add some aliases
|
# If it is the first user, add some aliases
|
||||||
if not ldap.search(base="ou=users,dc=yunohost,dc=org", filter="uid=*"):
|
if not ldap.search(base="ou=users", filter="uid=*"):
|
||||||
attr_dict["mail"] = [attr_dict["mail"]] + aliases
|
attr_dict["mail"] = [attr_dict["mail"]] + aliases
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ldap.add("uid=%s,ou=users" % username, attr_dict)
|
ldap.add(f"uid={username},ou=users", attr_dict)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError("user_creation_failed", user=username, error=e)
|
raise YunohostError("user_creation_failed", user=username, error=e)
|
||||||
|
|
||||||
|
@ -256,10 +256,10 @@ def user_create(
|
||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
["setfacl", "-m", "g:all_users:---", "/home/%s" % username]
|
["setfacl", "-m", "g:all_users:---", f"/home/{username}"]
|
||||||
)
|
)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
logger.warning("Failed to protect /home/%s" % username, exc_info=1)
|
logger.warning(f"Failed to protect /home/{username}", exc_info=1)
|
||||||
|
|
||||||
# Create group for user and add to group 'all_users'
|
# Create group for user and add to group 'all_users'
|
||||||
user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False)
|
user_group_create(groupname=username, gid=uid, primary_group=True, sync_perm=False)
|
||||||
|
@ -319,7 +319,7 @@ def user_delete(operation_logger, username, purge=False, from_import=False):
|
||||||
|
|
||||||
ldap = _get_ldap_interface()
|
ldap = _get_ldap_interface()
|
||||||
try:
|
try:
|
||||||
ldap.remove("uid=%s,ou=users" % username)
|
ldap.remove(f"uid={username},ou=users")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError("user_deletion_failed", user=username, error=e)
|
raise YunohostError("user_deletion_failed", user=username, error=e)
|
||||||
|
|
||||||
|
@ -378,7 +378,7 @@ def user_update(
|
||||||
ldap = _get_ldap_interface()
|
ldap = _get_ldap_interface()
|
||||||
attrs_to_fetch = ["givenName", "sn", "mail", "maildrop"]
|
attrs_to_fetch = ["givenName", "sn", "mail", "maildrop"]
|
||||||
result = ldap.search(
|
result = ldap.search(
|
||||||
base="ou=users,dc=yunohost,dc=org",
|
base="ou=users",
|
||||||
filter="uid=" + username,
|
filter="uid=" + username,
|
||||||
attrs=attrs_to_fetch,
|
attrs=attrs_to_fetch,
|
||||||
)
|
)
|
||||||
|
@ -507,7 +507,7 @@ def user_update(
|
||||||
operation_logger.start()
|
operation_logger.start()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ldap.update("uid=%s,ou=users" % username, new_attr_dict)
|
ldap.update(f"uid={username},ou=users", new_attr_dict)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError("user_update_failed", user=username, error=e)
|
raise YunohostError("user_update_failed", user=username, error=e)
|
||||||
|
|
||||||
|
@ -539,7 +539,7 @@ def user_info(username):
|
||||||
else:
|
else:
|
||||||
filter = "uid=" + username
|
filter = "uid=" + username
|
||||||
|
|
||||||
result = ldap.search("ou=users,dc=yunohost,dc=org", filter, user_attrs)
|
result = ldap.search("ou=users", filter, user_attrs)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
user = result[0]
|
user = result[0]
|
||||||
|
@ -578,11 +578,11 @@ def user_info(username):
|
||||||
logger.warning(m18n.n("mailbox_disabled", user=username))
|
logger.warning(m18n.n("mailbox_disabled", user=username))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
cmd = "doveadm -f flow quota get -u %s" % user["uid"][0]
|
uid_ = user["uid"][0]
|
||||||
cmd_result = check_output(cmd)
|
cmd_result = check_output(f"doveadm -f flow quota get -u {uid_}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
cmd_result = ""
|
cmd_result = ""
|
||||||
logger.warning("Failed to fetch quota info ... : %s " % str(e))
|
logger.warning(f"Failed to fetch quota info ... : {e}")
|
||||||
|
|
||||||
# Exemple of return value for cmd:
|
# Exemple of return value for cmd:
|
||||||
# """Quota name=User quota Type=STORAGE Value=0 Limit=- %=0
|
# """Quota name=User quota Type=STORAGE Value=0 Limit=- %=0
|
||||||
|
@ -708,8 +708,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False):
|
||||||
unknown_groups = [g for g in user["groups"] if g not in existing_groups]
|
unknown_groups = [g for g in user["groups"] if g not in existing_groups]
|
||||||
if unknown_groups:
|
if unknown_groups:
|
||||||
format_errors.append(
|
format_errors.append(
|
||||||
f"username '{user['username']}': unknown groups %s"
|
f"username '{user['username']}': unknown groups {', '.join(unknown_groups)}"
|
||||||
% ", ".join(unknown_groups)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate that domains exist
|
# Validate that domains exist
|
||||||
|
@ -730,8 +729,7 @@ def user_import(operation_logger, csvfile, update=False, delete=False):
|
||||||
|
|
||||||
if unknown_domains:
|
if unknown_domains:
|
||||||
format_errors.append(
|
format_errors.append(
|
||||||
f"username '{user['username']}': unknown domains %s"
|
f"username '{user['username']}': unknown domains {', '.join(unknown_domains)}"
|
||||||
% ", ".join(unknown_domains)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if format_errors:
|
if format_errors:
|
||||||
|
@ -939,7 +937,7 @@ def user_group_list(short=False, full=False, include_primary_groups=True):
|
||||||
|
|
||||||
ldap = _get_ldap_interface()
|
ldap = _get_ldap_interface()
|
||||||
groups_infos = ldap.search(
|
groups_infos = ldap.search(
|
||||||
"ou=groups,dc=yunohost,dc=org",
|
"ou=groups",
|
||||||
"(objectclass=groupOfNamesYnh)",
|
"(objectclass=groupOfNamesYnh)",
|
||||||
["cn", "member", "permission"],
|
["cn", "member", "permission"],
|
||||||
)
|
)
|
||||||
|
@ -990,7 +988,7 @@ def user_group_create(
|
||||||
|
|
||||||
# Validate uniqueness of groupname in LDAP
|
# Validate uniqueness of groupname in LDAP
|
||||||
conflict = ldap.get_conflict(
|
conflict = ldap.get_conflict(
|
||||||
{"cn": groupname}, base_dn="ou=groups,dc=yunohost,dc=org"
|
{"cn": groupname}, base_dn="ou=groups"
|
||||||
)
|
)
|
||||||
if conflict:
|
if conflict:
|
||||||
raise YunohostValidationError("group_already_exist", group=groupname)
|
raise YunohostValidationError("group_already_exist", group=groupname)
|
||||||
|
@ -1003,7 +1001,7 @@ def user_group_create(
|
||||||
m18n.n("group_already_exist_on_system_but_removing_it", group=groupname)
|
m18n.n("group_already_exist_on_system_but_removing_it", group=groupname)
|
||||||
)
|
)
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
"sed --in-place '/^%s:/d' /etc/group" % groupname, shell=True
|
f"sed --in-place '/^{groupname}:/d' /etc/group", shell=True
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise YunohostValidationError(
|
raise YunohostValidationError(
|
||||||
|
@ -1033,7 +1031,7 @@ def user_group_create(
|
||||||
|
|
||||||
operation_logger.start()
|
operation_logger.start()
|
||||||
try:
|
try:
|
||||||
ldap.add("cn=%s,ou=groups" % groupname, attr_dict)
|
ldap.add(f"cn={groupname},ou=groups", attr_dict)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError("group_creation_failed", group=groupname, error=e)
|
raise YunohostError("group_creation_failed", group=groupname, error=e)
|
||||||
|
|
||||||
|
@ -1076,7 +1074,7 @@ def user_group_delete(operation_logger, groupname, force=False, sync_perm=True):
|
||||||
operation_logger.start()
|
operation_logger.start()
|
||||||
ldap = _get_ldap_interface()
|
ldap = _get_ldap_interface()
|
||||||
try:
|
try:
|
||||||
ldap.remove("cn=%s,ou=groups" % groupname)
|
ldap.remove(f"cn={groupname},ou=groups")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise YunohostError("group_deletion_failed", group=groupname, error=e)
|
raise YunohostError("group_deletion_failed", group=groupname, error=e)
|
||||||
|
|
||||||
|
@ -1172,7 +1170,7 @@ def user_group_update(
|
||||||
ldap = _get_ldap_interface()
|
ldap = _get_ldap_interface()
|
||||||
try:
|
try:
|
||||||
ldap.update(
|
ldap.update(
|
||||||
"cn=%s,ou=groups" % groupname,
|
f"cn={groupname},ou=groups",
|
||||||
{"member": set(new_group_dns), "memberUid": set(new_group)},
|
{"member": set(new_group_dns), "memberUid": set(new_group)},
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -1205,7 +1203,7 @@ def user_group_info(groupname):
|
||||||
|
|
||||||
# Fetch info for this group
|
# Fetch info for this group
|
||||||
result = ldap.search(
|
result = ldap.search(
|
||||||
"ou=groups,dc=yunohost,dc=org",
|
"ou=groups",
|
||||||
"cn=" + groupname,
|
"cn=" + groupname,
|
||||||
["cn", "member", "permission"],
|
["cn", "member", "permission"],
|
||||||
)
|
)
|
||||||
|
@ -1259,9 +1257,7 @@ def user_group_remove(groupname, usernames, force=False, sync_perm=True):
|
||||||
def user_permission_list(short=False, full=False, apps=[]):
|
def user_permission_list(short=False, full=False, apps=[]):
|
||||||
from yunohost.permission import user_permission_list
|
from yunohost.permission import user_permission_list
|
||||||
|
|
||||||
return user_permission_list(
|
return user_permission_list(short, full, absolute_urls=True, apps=apps)
|
||||||
short, full, absolute_urls=True, apps=apps
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def user_permission_update(permission, label=None, show_tile=None, sync_perm=True):
|
def user_permission_update(permission, label=None, show_tile=None, sync_perm=True):
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from moulinette.core import MoulinetteError
|
from moulinette.core import MoulinetteError, MoulinetteAuthenticationError
|
||||||
from moulinette import m18n
|
from moulinette import m18n
|
||||||
|
|
||||||
|
|
||||||
|
@ -60,3 +60,9 @@ class YunohostValidationError(YunohostError):
|
||||||
def content(self):
|
def content(self):
|
||||||
|
|
||||||
return {"error": self.strerror, "error_key": self.key, **self.kwargs}
|
return {"error": self.strerror, "error_key": self.key, **self.kwargs}
|
||||||
|
|
||||||
|
|
||||||
|
class YunohostAuthenticationError(MoulinetteAuthenticationError):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,8 @@ class LDAPInterface:
|
||||||
"""
|
"""
|
||||||
if not base:
|
if not base:
|
||||||
base = self.basedn
|
base = self.basedn
|
||||||
|
else:
|
||||||
|
base = base + "," + self.basedn
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = self.con.search_s(base, ldap.SCOPE_SUBTREE, filter, attrs)
|
result = self.con.search_s(base, ldap.SCOPE_SUBTREE, filter, attrs)
|
||||||
|
@ -241,7 +243,7 @@ class LDAPInterface:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
dn = rdn + "," + self.basedn
|
dn = rdn + "," + self.basedn
|
||||||
actual_entry = self.search(base=dn, attrs=None)
|
actual_entry = self.search(rdn, attrs=None)
|
||||||
ldif = modlist.modifyModlist(actual_entry[0], attr_dict, ignore_oldexistent=1)
|
ldif = modlist.modifyModlist(actual_entry[0], attr_dict, ignore_oldexistent=1)
|
||||||
|
|
||||||
if ldif == []:
|
if ldif == []:
|
||||||
|
|
|
@ -117,7 +117,7 @@ def _patch_legacy_php_versions(app_folder):
|
||||||
c = (
|
c = (
|
||||||
"sed -i "
|
"sed -i "
|
||||||
+ "".join(
|
+ "".join(
|
||||||
"-e 's@{pattern}@{replace}@g' ".format(pattern=p, replace=r)
|
f"-e 's@{p}@{r}@g' "
|
||||||
for p, r in LEGACY_PHP_VERSION_REPLACEMENTS
|
for p, r in LEGACY_PHP_VERSION_REPLACEMENTS
|
||||||
)
|
)
|
||||||
+ "%s" % filename
|
+ "%s" % filename
|
||||||
|
|
Loading…
Add table
Reference in a new issue